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.

Files changed (187) hide show
  1. omlish/__about__.py +7 -0
  2. omlish/__init__.py +0 -0
  3. omlish/argparse.py +223 -0
  4. omlish/asyncs/__init__.py +17 -0
  5. omlish/asyncs/anyio.py +23 -0
  6. omlish/asyncs/asyncio.py +19 -0
  7. omlish/asyncs/asyncs.py +76 -0
  8. omlish/asyncs/futures.py +179 -0
  9. omlish/asyncs/trio.py +11 -0
  10. omlish/c3.py +173 -0
  11. omlish/cached.py +9 -0
  12. omlish/check.py +231 -0
  13. omlish/collections/__init__.py +63 -0
  14. omlish/collections/_abc.py +156 -0
  15. omlish/collections/_io_abc.py +78 -0
  16. omlish/collections/cache/__init__.py +11 -0
  17. omlish/collections/cache/descriptor.py +188 -0
  18. omlish/collections/cache/impl.py +485 -0
  19. omlish/collections/cache/types.py +37 -0
  20. omlish/collections/coerce.py +337 -0
  21. omlish/collections/frozen.py +148 -0
  22. omlish/collections/identity.py +106 -0
  23. omlish/collections/indexed.py +75 -0
  24. omlish/collections/mappings.py +127 -0
  25. omlish/collections/ordered.py +81 -0
  26. omlish/collections/persistent.py +36 -0
  27. omlish/collections/skiplist.py +193 -0
  28. omlish/collections/sorted.py +126 -0
  29. omlish/collections/treap.py +228 -0
  30. omlish/collections/treapmap.py +144 -0
  31. omlish/collections/unmodifiable.py +174 -0
  32. omlish/collections/utils.py +110 -0
  33. omlish/configs/__init__.py +0 -0
  34. omlish/configs/flattening.py +147 -0
  35. omlish/configs/props.py +64 -0
  36. omlish/dataclasses/__init__.py +83 -0
  37. omlish/dataclasses/impl/__init__.py +6 -0
  38. omlish/dataclasses/impl/api.py +260 -0
  39. omlish/dataclasses/impl/as_.py +76 -0
  40. omlish/dataclasses/impl/exceptions.py +2 -0
  41. omlish/dataclasses/impl/fields.py +148 -0
  42. omlish/dataclasses/impl/frozen.py +55 -0
  43. omlish/dataclasses/impl/hashing.py +85 -0
  44. omlish/dataclasses/impl/init.py +173 -0
  45. omlish/dataclasses/impl/internals.py +118 -0
  46. omlish/dataclasses/impl/main.py +150 -0
  47. omlish/dataclasses/impl/metaclass.py +126 -0
  48. omlish/dataclasses/impl/metadata.py +74 -0
  49. omlish/dataclasses/impl/order.py +47 -0
  50. omlish/dataclasses/impl/params.py +150 -0
  51. omlish/dataclasses/impl/processing.py +16 -0
  52. omlish/dataclasses/impl/reflect.py +173 -0
  53. omlish/dataclasses/impl/replace.py +40 -0
  54. omlish/dataclasses/impl/repr.py +34 -0
  55. omlish/dataclasses/impl/simple.py +92 -0
  56. omlish/dataclasses/impl/slots.py +80 -0
  57. omlish/dataclasses/impl/utils.py +167 -0
  58. omlish/defs.py +193 -0
  59. omlish/dispatch/__init__.py +3 -0
  60. omlish/dispatch/dispatch.py +137 -0
  61. omlish/dispatch/functions.py +52 -0
  62. omlish/dispatch/methods.py +162 -0
  63. omlish/docker.py +149 -0
  64. omlish/dynamic.py +220 -0
  65. omlish/graphs/__init__.py +0 -0
  66. omlish/graphs/dot/__init__.py +19 -0
  67. omlish/graphs/dot/items.py +162 -0
  68. omlish/graphs/dot/rendering.py +147 -0
  69. omlish/graphs/dot/utils.py +30 -0
  70. omlish/graphs/trees.py +249 -0
  71. omlish/http/__init__.py +0 -0
  72. omlish/http/consts.py +20 -0
  73. omlish/http/wsgi.py +34 -0
  74. omlish/inject/__init__.py +85 -0
  75. omlish/inject/binder.py +12 -0
  76. omlish/inject/bindings.py +49 -0
  77. omlish/inject/eagers.py +21 -0
  78. omlish/inject/elements.py +43 -0
  79. omlish/inject/exceptions.py +49 -0
  80. omlish/inject/impl/__init__.py +0 -0
  81. omlish/inject/impl/bindings.py +19 -0
  82. omlish/inject/impl/elements.py +154 -0
  83. omlish/inject/impl/injector.py +182 -0
  84. omlish/inject/impl/inspect.py +98 -0
  85. omlish/inject/impl/private.py +109 -0
  86. omlish/inject/impl/providers.py +132 -0
  87. omlish/inject/impl/scopes.py +198 -0
  88. omlish/inject/injector.py +40 -0
  89. omlish/inject/inspect.py +14 -0
  90. omlish/inject/keys.py +43 -0
  91. omlish/inject/managed.py +24 -0
  92. omlish/inject/overrides.py +18 -0
  93. omlish/inject/private.py +29 -0
  94. omlish/inject/providers.py +111 -0
  95. omlish/inject/proxy.py +48 -0
  96. omlish/inject/scopes.py +84 -0
  97. omlish/inject/types.py +21 -0
  98. omlish/iterators.py +184 -0
  99. omlish/json.py +194 -0
  100. omlish/lang/__init__.py +112 -0
  101. omlish/lang/cached.py +267 -0
  102. omlish/lang/classes/__init__.py +24 -0
  103. omlish/lang/classes/abstract.py +74 -0
  104. omlish/lang/classes/restrict.py +137 -0
  105. omlish/lang/classes/simple.py +120 -0
  106. omlish/lang/classes/test/__init__.py +0 -0
  107. omlish/lang/classes/test/test_abstract.py +89 -0
  108. omlish/lang/classes/test/test_restrict.py +71 -0
  109. omlish/lang/classes/test/test_simple.py +58 -0
  110. omlish/lang/classes/test/test_virtual.py +72 -0
  111. omlish/lang/classes/virtual.py +130 -0
  112. omlish/lang/clsdct.py +67 -0
  113. omlish/lang/cmp.py +63 -0
  114. omlish/lang/contextmanagers.py +249 -0
  115. omlish/lang/datetimes.py +67 -0
  116. omlish/lang/descriptors.py +52 -0
  117. omlish/lang/functions.py +126 -0
  118. omlish/lang/imports.py +153 -0
  119. omlish/lang/iterables.py +54 -0
  120. omlish/lang/maybes.py +136 -0
  121. omlish/lang/objects.py +103 -0
  122. omlish/lang/resolving.py +50 -0
  123. omlish/lang/strings.py +128 -0
  124. omlish/lang/typing.py +92 -0
  125. omlish/libc.py +532 -0
  126. omlish/logs/__init__.py +9 -0
  127. omlish/logs/_abc.py +247 -0
  128. omlish/logs/configs.py +62 -0
  129. omlish/logs/filters.py +9 -0
  130. omlish/logs/formatters.py +67 -0
  131. omlish/logs/utils.py +20 -0
  132. omlish/marshal/__init__.py +52 -0
  133. omlish/marshal/any.py +25 -0
  134. omlish/marshal/base.py +201 -0
  135. omlish/marshal/base64.py +25 -0
  136. omlish/marshal/dataclasses.py +115 -0
  137. omlish/marshal/datetimes.py +90 -0
  138. omlish/marshal/enums.py +43 -0
  139. omlish/marshal/exceptions.py +7 -0
  140. omlish/marshal/factories.py +129 -0
  141. omlish/marshal/global_.py +33 -0
  142. omlish/marshal/iterables.py +57 -0
  143. omlish/marshal/mappings.py +66 -0
  144. omlish/marshal/naming.py +17 -0
  145. omlish/marshal/objects.py +106 -0
  146. omlish/marshal/optionals.py +49 -0
  147. omlish/marshal/polymorphism.py +147 -0
  148. omlish/marshal/primitives.py +43 -0
  149. omlish/marshal/registries.py +57 -0
  150. omlish/marshal/standard.py +80 -0
  151. omlish/marshal/utils.py +23 -0
  152. omlish/marshal/uuids.py +29 -0
  153. omlish/marshal/values.py +30 -0
  154. omlish/math.py +184 -0
  155. omlish/os.py +32 -0
  156. omlish/reflect.py +359 -0
  157. omlish/replserver/__init__.py +5 -0
  158. omlish/replserver/__main__.py +4 -0
  159. omlish/replserver/console.py +247 -0
  160. omlish/replserver/server.py +146 -0
  161. omlish/runmodule.py +28 -0
  162. omlish/stats.py +342 -0
  163. omlish/term.py +222 -0
  164. omlish/testing/__init__.py +7 -0
  165. omlish/testing/pydevd.py +225 -0
  166. omlish/testing/pytest/__init__.py +8 -0
  167. omlish/testing/pytest/helpers.py +35 -0
  168. omlish/testing/pytest/inject/__init__.py +1 -0
  169. omlish/testing/pytest/inject/harness.py +159 -0
  170. omlish/testing/pytest/plugins/__init__.py +20 -0
  171. omlish/testing/pytest/plugins/_registry.py +6 -0
  172. omlish/testing/pytest/plugins/logging.py +13 -0
  173. omlish/testing/pytest/plugins/pycharm.py +54 -0
  174. omlish/testing/pytest/plugins/repeat.py +19 -0
  175. omlish/testing/pytest/plugins/skips.py +32 -0
  176. omlish/testing/pytest/plugins/spacing.py +19 -0
  177. omlish/testing/pytest/plugins/switches.py +70 -0
  178. omlish/testing/testing.py +102 -0
  179. omlish/text/__init__.py +0 -0
  180. omlish/text/delimit.py +171 -0
  181. omlish/text/indent.py +50 -0
  182. omlish/text/parts.py +265 -0
  183. omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
  184. omlish-0.0.0.dev1.dist-info/METADATA +17 -0
  185. omlish-0.0.0.dev1.dist-info/RECORD +187 -0
  186. omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
  187. 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('@', '&#64;')
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