omlish 0.0.0.dev80__py3-none-any.whl → 0.0.0.dev81__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 -4
- omlish/formats/json/cli.py +95 -31
- omlish/formats/json/render.py +4 -0
- omlish/formats/json/stream.py +389 -0
- omlish/genmachine.py +43 -9
- omlish/lang/resources.py +6 -1
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.dist-info}/METADATA +5 -5
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.dist-info}/RECORD +12 -11
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev80.dist-info → omlish-0.0.0.dev81.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.dev81'
|
2
|
+
__revision__ = 'a6b71055e7603077b28ba1999d389905e5663aac'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -45,7 +45,7 @@ class Project(ProjectBase):
|
|
45
45
|
'lz4 ~= 4.3',
|
46
46
|
# 'lz4 @ git+https://github.com/wrmsr/python-lz4@wrmsr_20240830_GIL_NOT_USED'
|
47
47
|
|
48
|
-
'python-snappy ~= 0.7
|
48
|
+
'python-snappy ~= 0.7',
|
49
49
|
|
50
50
|
'zstd ~= 1.5',
|
51
51
|
],
|
@@ -97,7 +97,7 @@ class Project(ProjectBase):
|
|
97
97
|
|
98
98
|
'aiomysql ~= 0.2',
|
99
99
|
'aiosqlite ~= 0.20',
|
100
|
-
'asyncpg ~= 0.30
|
100
|
+
'asyncpg ~= 0.30',
|
101
101
|
|
102
102
|
'apsw ~= 3.46',
|
103
103
|
|
omlish/formats/json/cli.py
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
import argparse
|
2
|
+
import codecs
|
2
3
|
import contextlib
|
3
4
|
import dataclasses as dc
|
4
5
|
import enum
|
6
|
+
import io
|
5
7
|
import json
|
8
|
+
import os
|
6
9
|
import subprocess
|
7
10
|
import sys
|
8
11
|
import typing as ta
|
9
12
|
|
13
|
+
from ... import check
|
10
14
|
from ... import lang
|
11
15
|
from ... import term
|
12
16
|
from .render import JsonRenderer
|
17
|
+
from .stream import JsonStreamLexer
|
18
|
+
from .stream import JsonStreamValueBuilder
|
13
19
|
|
14
20
|
|
15
21
|
if ta.TYPE_CHECKING:
|
@@ -67,15 +73,25 @@ def _main() -> None:
|
|
67
73
|
parser = argparse.ArgumentParser()
|
68
74
|
|
69
75
|
parser.add_argument('file', nargs='?')
|
76
|
+
|
77
|
+
parser.add_argument('--stream', action='store_true')
|
78
|
+
parser.add_argument('--stream-buffer-size', type=int, default=0x1000)
|
79
|
+
|
70
80
|
parser.add_argument('-f', '--format')
|
81
|
+
|
71
82
|
parser.add_argument('-z', '--compact', action='store_true')
|
72
83
|
parser.add_argument('-p', '--pretty', action='store_true')
|
73
84
|
parser.add_argument('-i', '--indent')
|
74
85
|
parser.add_argument('-s', '--sort-keys', action='store_true')
|
86
|
+
|
75
87
|
parser.add_argument('-c', '--color', action='store_true')
|
88
|
+
|
76
89
|
parser.add_argument('-l', '--less', action='store_true')
|
90
|
+
|
77
91
|
args = parser.parse_args()
|
78
92
|
|
93
|
+
#
|
94
|
+
|
79
95
|
separators = None
|
80
96
|
if args.compact:
|
81
97
|
separators = (',', ':')
|
@@ -89,6 +105,28 @@ def _main() -> None:
|
|
89
105
|
except ValueError:
|
90
106
|
indent = args.indent
|
91
107
|
|
108
|
+
kw: dict[str, ta.Any] = dict(
|
109
|
+
indent=indent,
|
110
|
+
separators=separators,
|
111
|
+
sort_keys=args.sort_keys,
|
112
|
+
)
|
113
|
+
|
114
|
+
def render_one(v: ta.Any) -> str:
|
115
|
+
if args.color:
|
116
|
+
return JsonRenderer.render_str(
|
117
|
+
v,
|
118
|
+
**kw,
|
119
|
+
style=term_color,
|
120
|
+
)
|
121
|
+
|
122
|
+
else:
|
123
|
+
return json.dumps(
|
124
|
+
v,
|
125
|
+
**kw,
|
126
|
+
)
|
127
|
+
|
128
|
+
#
|
129
|
+
|
92
130
|
fmt_name = args.format
|
93
131
|
if fmt_name is None:
|
94
132
|
if args.file is not None:
|
@@ -99,45 +137,71 @@ def _main() -> None:
|
|
99
137
|
fmt_name = 'json'
|
100
138
|
fmt = FORMATS_BY_NAME[fmt_name]
|
101
139
|
|
140
|
+
if args.stream:
|
141
|
+
check.arg(fmt is Formats.JSON.value)
|
142
|
+
|
143
|
+
#
|
144
|
+
|
102
145
|
with contextlib.ExitStack() as es:
|
103
146
|
if args.file is None:
|
104
|
-
in_file = sys.stdin
|
147
|
+
in_file = sys.stdin.buffer
|
148
|
+
|
105
149
|
else:
|
106
|
-
in_file = es.enter_context(open(args.file))
|
150
|
+
in_file = es.enter_context(open(args.file, 'rb'))
|
107
151
|
|
108
|
-
|
152
|
+
#
|
109
153
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
154
|
+
if args.less:
|
155
|
+
less = subprocess.Popen(
|
156
|
+
[
|
157
|
+
'less',
|
158
|
+
*(['-R'] if args.color else []),
|
159
|
+
],
|
160
|
+
stdin=subprocess.PIPE,
|
161
|
+
encoding='utf-8',
|
162
|
+
)
|
163
|
+
out = check.not_none(less.stdin)
|
115
164
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
**kw,
|
120
|
-
style=term_color,
|
121
|
-
)
|
165
|
+
def close_less():
|
166
|
+
out.close()
|
167
|
+
less.wait()
|
122
168
|
|
123
|
-
|
124
|
-
out = json.dumps(
|
125
|
-
data,
|
126
|
-
**kw,
|
127
|
-
)
|
128
|
-
|
129
|
-
if args.less:
|
130
|
-
subprocess.run(
|
131
|
-
[
|
132
|
-
'less',
|
133
|
-
*(['-R'] if args.color else []),
|
134
|
-
],
|
135
|
-
input=out.encode(),
|
136
|
-
check=True,
|
137
|
-
)
|
169
|
+
es.enter_context(lang.defer(close_less)) # noqa
|
138
170
|
|
139
|
-
|
140
|
-
|
171
|
+
else:
|
172
|
+
out = sys.stdout
|
173
|
+
|
174
|
+
#
|
175
|
+
|
176
|
+
if args.stream:
|
177
|
+
fd = in_file.fileno()
|
178
|
+
decoder = codecs.getincrementaldecoder('utf-8')()
|
179
|
+
|
180
|
+
with contextlib.ExitStack() as es2:
|
181
|
+
lex = es2.enter_context(JsonStreamLexer())
|
182
|
+
vb = es2.enter_context(JsonStreamValueBuilder())
|
183
|
+
|
184
|
+
while True:
|
185
|
+
buf = os.read(fd, args.stream_buffer_size)
|
186
|
+
|
187
|
+
for s in decoder.decode(buf, not buf):
|
188
|
+
n = 0
|
189
|
+
for c in s:
|
190
|
+
for t in lex(c):
|
191
|
+
for v in vb(t):
|
192
|
+
print(render_one(v), file=out)
|
193
|
+
n += 1
|
194
|
+
|
195
|
+
if n:
|
196
|
+
out.flush()
|
197
|
+
|
198
|
+
if not buf:
|
199
|
+
break
|
200
|
+
|
201
|
+
else:
|
202
|
+
with io.TextIOWrapper(in_file) as tw:
|
203
|
+
v = fmt.load(tw)
|
204
|
+
print(render_one(v), file=out)
|
141
205
|
|
142
206
|
|
143
207
|
if __name__ == '__main__':
|
omlish/formats/json/render.py
CHANGED
@@ -0,0 +1,389 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import io
|
3
|
+
import json
|
4
|
+
import re
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ... import check
|
8
|
+
from ...genmachine import GenMachine
|
9
|
+
|
10
|
+
|
11
|
+
ValueTokenKind: ta.TypeAlias = ta.Literal[
|
12
|
+
'STRING',
|
13
|
+
'NUMBER',
|
14
|
+
|
15
|
+
'SPECIAL_NUMBER',
|
16
|
+
'BOOLEAN',
|
17
|
+
'NULL',
|
18
|
+
]
|
19
|
+
|
20
|
+
ControlTokenKind: ta.TypeAlias = ta.Literal[
|
21
|
+
'LBRACE',
|
22
|
+
'RBRACE',
|
23
|
+
'LBRACKET',
|
24
|
+
'RBRACKET',
|
25
|
+
'COMMA',
|
26
|
+
'COLON',
|
27
|
+
]
|
28
|
+
|
29
|
+
TokenKind: ta.TypeAlias = ValueTokenKind | ControlTokenKind
|
30
|
+
|
31
|
+
TokenValue: ta.TypeAlias = str | float | int | None
|
32
|
+
|
33
|
+
|
34
|
+
class Token(ta.NamedTuple):
|
35
|
+
kind: TokenKind
|
36
|
+
value: TokenValue
|
37
|
+
raw: str
|
38
|
+
|
39
|
+
ofs: int
|
40
|
+
line: int
|
41
|
+
col: int
|
42
|
+
|
43
|
+
def __iter__(self):
|
44
|
+
raise TypeError
|
45
|
+
|
46
|
+
|
47
|
+
NUMBER_PAT = re.compile(r'-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?')
|
48
|
+
|
49
|
+
VALUE_TOKEN_KINDS = frozenset(check.isinstance(a, str) for a in ta.get_args(ValueTokenKind))
|
50
|
+
|
51
|
+
CONTROL_TOKENS: ta.Mapping[str, TokenKind] = {
|
52
|
+
'{': 'LBRACE',
|
53
|
+
'}': 'RBRACE',
|
54
|
+
'[': 'LBRACKET',
|
55
|
+
']': 'RBRACKET',
|
56
|
+
',': 'COMMA',
|
57
|
+
':': 'COLON',
|
58
|
+
}
|
59
|
+
|
60
|
+
CONST_TOKENS: ta.Mapping[str, tuple[TokenKind, str | float | None]] = {
|
61
|
+
'NaN': ('SPECIAL_NUMBER', float('nan')),
|
62
|
+
'Infinity': ('SPECIAL_NUMBER', float('inf')),
|
63
|
+
'-Infinity': ('SPECIAL_NUMBER', float('-inf')),
|
64
|
+
|
65
|
+
'true': ('BOOLEAN', True),
|
66
|
+
'false': ('BOOLEAN', False),
|
67
|
+
'null': ('NULL', None),
|
68
|
+
}
|
69
|
+
|
70
|
+
|
71
|
+
@dc.dataclass(frozen=True)
|
72
|
+
class JsonLexError(Exception):
|
73
|
+
message: str
|
74
|
+
|
75
|
+
ofs: int
|
76
|
+
line: int
|
77
|
+
col: int
|
78
|
+
|
79
|
+
|
80
|
+
class JsonStreamLexer(GenMachine[str, Token]):
|
81
|
+
def __init__(self) -> None:
|
82
|
+
self._ofs = 0
|
83
|
+
self._line = 0
|
84
|
+
self._col = 0
|
85
|
+
|
86
|
+
self._buf = io.StringIO()
|
87
|
+
|
88
|
+
super().__init__(self._do_main())
|
89
|
+
|
90
|
+
def _char_in(self, c: str) -> str:
|
91
|
+
if len(c) != 1:
|
92
|
+
raise ValueError(c)
|
93
|
+
|
94
|
+
self._ofs += 1
|
95
|
+
|
96
|
+
if c == '\n':
|
97
|
+
self._line += 1
|
98
|
+
self._col = 0
|
99
|
+
else:
|
100
|
+
self._col += 1
|
101
|
+
|
102
|
+
return c
|
103
|
+
|
104
|
+
def _make_tok(
|
105
|
+
self,
|
106
|
+
kind: TokenKind,
|
107
|
+
value: TokenValue,
|
108
|
+
raw: str,
|
109
|
+
) -> ta.Sequence[Token]:
|
110
|
+
tok = Token(
|
111
|
+
kind,
|
112
|
+
value,
|
113
|
+
raw,
|
114
|
+
self._ofs,
|
115
|
+
self._line,
|
116
|
+
self._col,
|
117
|
+
)
|
118
|
+
return (tok,)
|
119
|
+
|
120
|
+
def _flip_buf(self) -> str:
|
121
|
+
raw = self._buf.getvalue()
|
122
|
+
self._buf.seek(0)
|
123
|
+
self._buf.truncate()
|
124
|
+
return raw
|
125
|
+
|
126
|
+
def _raise(self, msg: str) -> ta.NoReturn:
|
127
|
+
raise JsonLexError(msg, self._ofs, self._line, self._col)
|
128
|
+
|
129
|
+
def _do_main(self):
|
130
|
+
while True:
|
131
|
+
c = self._char_in((yield None)) # noqa
|
132
|
+
|
133
|
+
if c.isspace():
|
134
|
+
continue
|
135
|
+
|
136
|
+
if c in CONTROL_TOKENS:
|
137
|
+
yield self._make_tok(CONTROL_TOKENS[c], c, c)
|
138
|
+
continue
|
139
|
+
|
140
|
+
if c == '"':
|
141
|
+
return self._do_string()
|
142
|
+
|
143
|
+
if c.isdigit() or c == '-':
|
144
|
+
return self._do_number(c)
|
145
|
+
|
146
|
+
if c in 'tfnIN':
|
147
|
+
return self._do_const(c)
|
148
|
+
|
149
|
+
self._raise(f'Unexpected character: {c}')
|
150
|
+
|
151
|
+
def _do_string(self):
|
152
|
+
self._buf.write('"')
|
153
|
+
|
154
|
+
last = None
|
155
|
+
while True:
|
156
|
+
try:
|
157
|
+
c = self._char_in((yield None)) # noqa
|
158
|
+
except GeneratorExit:
|
159
|
+
self._raise('Unexpected end of input')
|
160
|
+
|
161
|
+
self._buf.write(c)
|
162
|
+
if c == '"' and last != '\\':
|
163
|
+
break
|
164
|
+
last = c
|
165
|
+
|
166
|
+
raw = self._flip_buf()
|
167
|
+
sv = json.loads(raw)
|
168
|
+
yield self._make_tok('STRING', sv, raw)
|
169
|
+
|
170
|
+
return self._do_main()
|
171
|
+
|
172
|
+
def _do_number(self, c: str):
|
173
|
+
self._buf.write(c)
|
174
|
+
|
175
|
+
while True:
|
176
|
+
try:
|
177
|
+
c = self._char_in((yield None)) # noqa
|
178
|
+
except GeneratorExit:
|
179
|
+
self._raise('Unexpected end of input')
|
180
|
+
|
181
|
+
if not (c.isdigit() or c in '.eE+-'):
|
182
|
+
break
|
183
|
+
self._buf.write(c)
|
184
|
+
|
185
|
+
raw = self._flip_buf()
|
186
|
+
if not NUMBER_PAT.fullmatch(raw):
|
187
|
+
raw += c
|
188
|
+
try:
|
189
|
+
for _ in range(7):
|
190
|
+
raw += self._char_in((yield None)) # noqa
|
191
|
+
except GeneratorExit:
|
192
|
+
self._raise('Unexpected end of input')
|
193
|
+
|
194
|
+
if raw != '-Infinity':
|
195
|
+
self._raise(f'Invalid number format: {raw}')
|
196
|
+
|
197
|
+
tk, tv = CONST_TOKENS[raw]
|
198
|
+
yield self._make_tok(tk, tv, raw)
|
199
|
+
|
200
|
+
return self._do_main()
|
201
|
+
|
202
|
+
nv = float(raw) if '.' in raw or 'e' in raw or 'E' in raw else int(raw)
|
203
|
+
yield self._make_tok('NUMBER', nv, raw)
|
204
|
+
|
205
|
+
if c in CONTROL_TOKENS:
|
206
|
+
yield self._make_tok(CONTROL_TOKENS[c], c, c)
|
207
|
+
elif not c.isspace():
|
208
|
+
self._raise(f'Unexpected character after number: {c}')
|
209
|
+
|
210
|
+
return self._do_main()
|
211
|
+
|
212
|
+
def _do_const(self, c: str):
|
213
|
+
raw = c
|
214
|
+
while True:
|
215
|
+
try:
|
216
|
+
raw += self._char_in((yield None)) # noqa
|
217
|
+
except GeneratorExit:
|
218
|
+
self._raise('Unexpected end of input')
|
219
|
+
|
220
|
+
if raw in CONST_TOKENS:
|
221
|
+
break
|
222
|
+
|
223
|
+
if len(raw) > 8: # None of the keywords are longer than 8 characters
|
224
|
+
self._raise(f'Invalid literal: {raw}')
|
225
|
+
|
226
|
+
tk, tv = CONST_TOKENS[raw]
|
227
|
+
yield self._make_tok(tk, tv, raw)
|
228
|
+
|
229
|
+
return self._do_main()
|
230
|
+
|
231
|
+
|
232
|
+
class JsonStreamObject(list):
|
233
|
+
def __repr__(self) -> str:
|
234
|
+
return f'{self.__class__.__name__}({super().__repr__()})'
|
235
|
+
|
236
|
+
|
237
|
+
class JsonStreamValueBuilder(GenMachine[Token, ta.Any]):
|
238
|
+
def __init__(
|
239
|
+
self,
|
240
|
+
*,
|
241
|
+
yield_object_lists: bool = False,
|
242
|
+
) -> None:
|
243
|
+
super().__init__(self._do_value())
|
244
|
+
|
245
|
+
self._yield_object_lists = yield_object_lists
|
246
|
+
|
247
|
+
self._stack: list[
|
248
|
+
tuple[ta.Literal['OBJECT'], JsonStreamObject] |
|
249
|
+
tuple[ta.Literal['PAIR'], str] |
|
250
|
+
tuple[ta.Literal['ARRAY'], list]
|
251
|
+
] = []
|
252
|
+
|
253
|
+
#
|
254
|
+
|
255
|
+
def _emit_value(self, v):
|
256
|
+
if not self._stack:
|
257
|
+
return ((v,), self._do_value())
|
258
|
+
|
259
|
+
tt, tv = self._stack[-1]
|
260
|
+
if tt == 'PAIR':
|
261
|
+
self._stack.pop()
|
262
|
+
if not self._stack:
|
263
|
+
raise self.StateError
|
264
|
+
|
265
|
+
tt2, tv2 = self._stack[-1]
|
266
|
+
if tt2 == 'OBJECT':
|
267
|
+
tv2.append((tv, v)) # type: ignore
|
268
|
+
return ((), self._do_after_pair())
|
269
|
+
|
270
|
+
else:
|
271
|
+
raise self.StateError
|
272
|
+
|
273
|
+
elif tt == 'ARRAY':
|
274
|
+
tv.append(v) # type: ignore
|
275
|
+
return ((), self._do_after_element())
|
276
|
+
|
277
|
+
else:
|
278
|
+
raise self.StateError
|
279
|
+
|
280
|
+
#
|
281
|
+
|
282
|
+
def _do_value(self):
|
283
|
+
try:
|
284
|
+
tok = yield None
|
285
|
+
except GeneratorExit:
|
286
|
+
if self._stack:
|
287
|
+
raise self.StateError from None
|
288
|
+
else:
|
289
|
+
raise
|
290
|
+
|
291
|
+
if tok.kind in VALUE_TOKEN_KINDS:
|
292
|
+
y, r = self._emit_value(tok.value)
|
293
|
+
yield y
|
294
|
+
return r
|
295
|
+
|
296
|
+
elif tok.kind == 'LBRACE':
|
297
|
+
return self._do_object()
|
298
|
+
|
299
|
+
elif tok.kind == 'LBRACKET':
|
300
|
+
return self._do_array()
|
301
|
+
|
302
|
+
else:
|
303
|
+
raise self.StateError
|
304
|
+
|
305
|
+
#
|
306
|
+
|
307
|
+
def _do_object(self):
|
308
|
+
self._stack.append(('OBJECT', JsonStreamObject()))
|
309
|
+
return self._do_object_body()
|
310
|
+
|
311
|
+
def _do_object_body(self):
|
312
|
+
try:
|
313
|
+
tok = yield None
|
314
|
+
except GeneratorExit:
|
315
|
+
raise self.StateError from None
|
316
|
+
|
317
|
+
if tok.kind == 'STRING':
|
318
|
+
k = tok.value
|
319
|
+
|
320
|
+
try:
|
321
|
+
tok = yield None
|
322
|
+
except GeneratorExit:
|
323
|
+
raise self.StateError from None
|
324
|
+
if tok.kind != 'COLON':
|
325
|
+
raise self.StateError
|
326
|
+
|
327
|
+
self._stack.append(('PAIR', k))
|
328
|
+
return self._do_value()
|
329
|
+
|
330
|
+
else:
|
331
|
+
raise self.StateError
|
332
|
+
|
333
|
+
def _do_after_pair(self):
|
334
|
+
try:
|
335
|
+
tok = yield None
|
336
|
+
except GeneratorExit:
|
337
|
+
raise self.StateError from None
|
338
|
+
|
339
|
+
if tok.kind == 'COMMA':
|
340
|
+
return self._do_object_body()
|
341
|
+
|
342
|
+
elif tok.kind == 'RBRACE':
|
343
|
+
if not self._stack:
|
344
|
+
raise self.StateError
|
345
|
+
|
346
|
+
tv: ta.Any
|
347
|
+
tt, tv = self._stack.pop()
|
348
|
+
if tt != 'OBJECT':
|
349
|
+
raise self.StateError
|
350
|
+
|
351
|
+
if not self._yield_object_lists:
|
352
|
+
tv = dict(tv)
|
353
|
+
|
354
|
+
y, r = self._emit_value(tv)
|
355
|
+
yield y
|
356
|
+
return r
|
357
|
+
|
358
|
+
else:
|
359
|
+
raise self.StateError
|
360
|
+
|
361
|
+
#
|
362
|
+
|
363
|
+
def _do_array(self):
|
364
|
+
self._stack.append(('ARRAY', []))
|
365
|
+
return self._do_value()
|
366
|
+
|
367
|
+
def _do_after_element(self):
|
368
|
+
try:
|
369
|
+
tok = yield None
|
370
|
+
except GeneratorExit:
|
371
|
+
raise self.StateError from None
|
372
|
+
|
373
|
+
if tok.kind == 'COMMA':
|
374
|
+
return self._do_value()
|
375
|
+
|
376
|
+
elif tok.kind == 'RBRACKET':
|
377
|
+
if not self._stack:
|
378
|
+
raise self.StateError
|
379
|
+
|
380
|
+
tt, tv = self._stack.pop()
|
381
|
+
if tt != 'ARRAY':
|
382
|
+
raise self.StateError
|
383
|
+
|
384
|
+
y, r = self._emit_value(tv)
|
385
|
+
yield y
|
386
|
+
return r
|
387
|
+
|
388
|
+
else:
|
389
|
+
raise self.StateError
|
omlish/genmachine.py
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
"""
|
2
|
+
TODO:
|
3
|
+
- feed_iter helper
|
4
|
+
|
2
5
|
See:
|
3
6
|
- https://github.com/pytransitions/transitions
|
4
7
|
"""
|
@@ -15,10 +18,6 @@ MachineGen: ta.TypeAlias = ta.Generator[ta.Any, ta.Any, ta.Any]
|
|
15
18
|
##
|
16
19
|
|
17
20
|
|
18
|
-
class IllegalStateError(Exception):
|
19
|
-
pass
|
20
|
-
|
21
|
-
|
22
21
|
class GenMachine(ta.Generic[I, O]):
|
23
22
|
"""
|
24
23
|
Generator-powered state machine. Generators are sent an `I` object and yield any number of `O` objects in response,
|
@@ -30,30 +29,65 @@ class GenMachine(ta.Generic[I, O]):
|
|
30
29
|
super().__init__()
|
31
30
|
self._advance(initial)
|
32
31
|
|
32
|
+
_gen: MachineGen | None
|
33
|
+
|
34
|
+
def __repr__(self) -> str:
|
35
|
+
return f'{self.__class__.__name__}@{hex(id(self))[2:]}<{self.state}>'
|
36
|
+
|
37
|
+
#
|
38
|
+
|
33
39
|
@property
|
34
40
|
def state(self) -> str | None:
|
35
41
|
if self._gen is not None:
|
36
42
|
return self._gen.gi_code.co_qualname
|
37
43
|
return None
|
38
44
|
|
39
|
-
|
40
|
-
return f'{self.__class__.__name__}@{hex(id(self))[2:]}<{self.state}>'
|
45
|
+
#
|
41
46
|
|
42
|
-
|
47
|
+
@property
|
48
|
+
def closed(self) -> bool:
|
49
|
+
return self._gen is None
|
50
|
+
|
51
|
+
def close(self) -> None:
|
52
|
+
if self._gen is not None:
|
53
|
+
self._gen.close()
|
54
|
+
self._gen = None
|
55
|
+
|
56
|
+
def __enter__(self) -> ta.Self:
|
57
|
+
return self
|
58
|
+
|
59
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
60
|
+
self.close()
|
61
|
+
|
62
|
+
#
|
63
|
+
|
64
|
+
class Error(Exception):
|
65
|
+
pass
|
66
|
+
|
67
|
+
class ClosedError(Exception):
|
68
|
+
pass
|
69
|
+
|
70
|
+
class StateError(Exception):
|
71
|
+
pass
|
72
|
+
|
73
|
+
#
|
43
74
|
|
44
75
|
def _advance(self, gen: MachineGen) -> None:
|
45
76
|
self._gen = gen
|
46
77
|
if (n := next(self._gen)) is not None: # noqa
|
47
|
-
raise
|
78
|
+
raise GenMachine.ClosedError
|
48
79
|
|
49
80
|
def __call__(self, i: I) -> ta.Iterable[O]:
|
50
81
|
if self._gen is None:
|
51
|
-
raise
|
82
|
+
raise GenMachine.ClosedError
|
83
|
+
|
52
84
|
try:
|
53
85
|
while (o := self._gen.send(i)) is not None:
|
54
86
|
yield from o
|
87
|
+
|
55
88
|
except StopIteration as s:
|
56
89
|
if s.value is None:
|
57
90
|
self._gen = None
|
58
91
|
return None
|
92
|
+
|
59
93
|
self._advance(s.value)
|
omlish/lang/resources.py
CHANGED
@@ -1,14 +1,19 @@
|
|
1
|
+
import dataclasses as dc
|
1
2
|
import functools
|
2
3
|
import importlib.resources
|
3
4
|
import os.path
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
|
7
|
-
|
8
|
+
@dc.dataclass(frozen=True)
|
9
|
+
class RelativeResource:
|
8
10
|
name: str
|
9
11
|
is_file: bool
|
10
12
|
read_bytes: ta.Callable[[], bytes]
|
11
13
|
|
14
|
+
def read_text(self, encoding: str = 'utf-8') -> str:
|
15
|
+
return self.read_bytes().decode(encoding)
|
16
|
+
|
12
17
|
|
13
18
|
def get_relative_resources(
|
14
19
|
path: str = '',
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: omlish
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev81
|
4
4
|
Summary: omlish
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -19,6 +19,7 @@ Requires-Dist: greenlet ~=3.1 ; extra == 'all'
|
|
19
19
|
Requires-Dist: trio ~=0.27 ; extra == 'all'
|
20
20
|
Requires-Dist: trio-asyncio ~=0.15 ; extra == 'all'
|
21
21
|
Requires-Dist: lz4 ~=4.3 ; extra == 'all'
|
22
|
+
Requires-Dist: python-snappy ~=0.7 ; extra == 'all'
|
22
23
|
Requires-Dist: zstd ~=1.5 ; extra == 'all'
|
23
24
|
Requires-Dist: asttokens ~=2.4 ; extra == 'all'
|
24
25
|
Requires-Dist: executing ~=2.1 ; extra == 'all'
|
@@ -37,11 +38,10 @@ Requires-Dist: pg8000 ~=1.31 ; extra == 'all'
|
|
37
38
|
Requires-Dist: pymysql ~=1.1 ; extra == 'all'
|
38
39
|
Requires-Dist: aiomysql ~=0.2 ; extra == 'all'
|
39
40
|
Requires-Dist: aiosqlite ~=0.20 ; extra == 'all'
|
41
|
+
Requires-Dist: asyncpg ~=0.30 ; extra == 'all'
|
40
42
|
Requires-Dist: apsw ~=3.46 ; extra == 'all'
|
41
43
|
Requires-Dist: duckdb ~=1.1 ; extra == 'all'
|
42
44
|
Requires-Dist: pytest ~=8.0 ; extra == 'all'
|
43
|
-
Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'all'
|
44
|
-
Requires-Dist: asyncpg ~=0.30 ; (python_version < "3.13") and extra == 'all'
|
45
45
|
Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'all'
|
46
46
|
Provides-Extra: async
|
47
47
|
Requires-Dist: anyio ~=4.6 ; extra == 'async'
|
@@ -51,8 +51,8 @@ Requires-Dist: trio ~=0.27 ; extra == 'async'
|
|
51
51
|
Requires-Dist: trio-asyncio ~=0.15 ; extra == 'async'
|
52
52
|
Provides-Extra: compress
|
53
53
|
Requires-Dist: lz4 ~=4.3 ; extra == 'compress'
|
54
|
+
Requires-Dist: python-snappy ~=0.7 ; extra == 'compress'
|
54
55
|
Requires-Dist: zstd ~=1.5 ; extra == 'compress'
|
55
|
-
Requires-Dist: python-snappy ~=0.7 ; (python_version < "3.13") and extra == 'compress'
|
56
56
|
Provides-Extra: diag
|
57
57
|
Requires-Dist: asttokens ~=2.4 ; extra == 'diag'
|
58
58
|
Requires-Dist: executing ~=2.1 ; extra == 'diag'
|
@@ -85,9 +85,9 @@ Requires-Dist: pg8000 ~=1.31 ; extra == 'sqldrivers'
|
|
85
85
|
Requires-Dist: pymysql ~=1.1 ; extra == 'sqldrivers'
|
86
86
|
Requires-Dist: aiomysql ~=0.2 ; extra == 'sqldrivers'
|
87
87
|
Requires-Dist: aiosqlite ~=0.20 ; extra == 'sqldrivers'
|
88
|
+
Requires-Dist: asyncpg ~=0.30 ; extra == 'sqldrivers'
|
88
89
|
Requires-Dist: apsw ~=3.46 ; extra == 'sqldrivers'
|
89
90
|
Requires-Dist: duckdb ~=1.1 ; extra == 'sqldrivers'
|
90
|
-
Requires-Dist: asyncpg ~=0.30 ; (python_version < "3.13") and extra == 'sqldrivers'
|
91
91
|
Requires-Dist: sqlean.py ~=3.45 ; (python_version < "3.13") and extra == 'sqldrivers'
|
92
92
|
Provides-Extra: testing
|
93
93
|
Requires-Dist: pytest ~=8.0 ; extra == 'testing'
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=ucaSu1XcJPryi-AqINUejkVDeJAFk7Bp5ar5_tJTgME,1692
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=ShI7he6zZJPcB446sYRskzCJpPLVOwTUy-2gII0BYpI,3370
|
3
3
|
omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
|
5
5
|
omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
|
@@ -10,7 +10,7 @@ omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
|
|
10
10
|
omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
|
11
11
|
omlish/fnpairs.py,sha256=Sl8CMFNyDS-1JYAjSWqnT5FmUm9Lj6o7FxSRo7g4jww,10875
|
12
12
|
omlish/fnpipes.py,sha256=AJkgz9nvRRm7oqw7ZgYyz21klu276LWi54oYCLg-vOg,2196
|
13
|
-
omlish/genmachine.py,sha256=
|
13
|
+
omlish/genmachine.py,sha256=zcFUbWHlX2qqNGTb6V8i9Hmbc7sta4DOry7B67mG-uY,2092
|
14
14
|
omlish/iterators.py,sha256=GGLC7RIT86uXMjhIIIqnff_Iu5SI_b9rXYywYGFyzmo,7292
|
15
15
|
omlish/libc.py,sha256=8r7Ejyhttk9ruCfBkxNTrlzir5WPbDE2vmY7VPlceMA,15362
|
16
16
|
omlish/matchfns.py,sha256=I1IlQGfEyk_AcFSy6ulVS3utC-uwyZM2YfUXYHc9Bw0,6152
|
@@ -184,9 +184,10 @@ omlish/formats/props.py,sha256=JwFJbKblqzqnzXf7YKFzQSDfcAXzkKsfoYvad6FPy98,18945
|
|
184
184
|
omlish/formats/yaml.py,sha256=wTW8ECG9jyA7qIFUqKZUro4KAKpN4IvcW_qhlrKveXM,6836
|
185
185
|
omlish/formats/json/__init__.py,sha256=moSR67Qkju2eYb_qVDtaivepe44mxAnYuC8OCSbtETg,298
|
186
186
|
omlish/formats/json/__main__.py,sha256=1wxxKZVkj_u7HCcewwMIbGuZj_Wph95yrUbm474Op9M,188
|
187
|
-
omlish/formats/json/cli.py,sha256=
|
187
|
+
omlish/formats/json/cli.py,sha256=FJuVzXBx9ysd31fObwGeiCyFz7F0Mx7I9nC9vbjsbCs,5195
|
188
188
|
omlish/formats/json/json.py,sha256=y8d8WWgzGZDTjzYc_xe9v4T0foXHI-UP7gjCwnHzUIA,828
|
189
|
-
omlish/formats/json/render.py,sha256=
|
189
|
+
omlish/formats/json/render.py,sha256=7p-Q_nq9MwmlZkKHVDY58yVCkztePeZXnimiROoMDko,3239
|
190
|
+
omlish/formats/json/stream.py,sha256=oGP91ivGEoPzC1Vv-cMDZHCqnt2Cq4WLswYTv39Bayk,9167
|
190
191
|
omlish/formats/json/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
191
192
|
omlish/formats/json/backends/orjson.py,sha256=GYZx0zgpxwkJbFh4EJLGa6VMoEK-Q6mf5tQp8aXqTDc,2526
|
192
193
|
omlish/formats/json/backends/std.py,sha256=00NdUFT9GeWL1EWbgKhWLboDBIuDxr7EiizPZXbRWrc,1973
|
@@ -257,7 +258,7 @@ omlish/lang/iterables.py,sha256=xRwktm6i2RHSb_ELfAXdjITIfE69qDyMEzgeZqvQXiU,2386
|
|
257
258
|
omlish/lang/maybes.py,sha256=NYHZDjqDtwPMheDrj2VtUVujxRPf8Qpgk4ZlZCTvBZc,3492
|
258
259
|
omlish/lang/objects.py,sha256=LOC3JvX1g5hPxJ7Sv2TK9kNkAo9c8J-Jw2NmClR_rkA,4576
|
259
260
|
omlish/lang/resolving.py,sha256=OuN2mDTPNyBUbcrswtvFKtj4xgH4H4WglgqSKv3MTy0,1606
|
260
|
-
omlish/lang/resources.py,sha256
|
261
|
+
omlish/lang/resources.py,sha256=yywDWhh0tsgw24l7mHYv49ll4oZS8Kc8MSCa8f4UbbI,2280
|
261
262
|
omlish/lang/strings.py,sha256=BsciSYnckD4vGtC6kmtnugR9IN6CIHdcjO4nZu-pSAw,3898
|
262
263
|
omlish/lang/sys.py,sha256=UoZz_PJYVKLQAKqYxxn-LHz1okK_38I__maZgnXMcxU,406
|
263
264
|
omlish/lang/timeouts.py,sha256=vECdWYhc_IZgcal1Ng1Y42wf2FV3KAx-i8As-MgGHIQ,1186
|
@@ -439,9 +440,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
|
439
440
|
omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
|
440
441
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
441
442
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
442
|
-
omlish-0.0.0.
|
443
|
-
omlish-0.0.0.
|
444
|
-
omlish-0.0.0.
|
445
|
-
omlish-0.0.0.
|
446
|
-
omlish-0.0.0.
|
447
|
-
omlish-0.0.0.
|
443
|
+
omlish-0.0.0.dev81.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
444
|
+
omlish-0.0.0.dev81.dist-info/METADATA,sha256=UooryMHMgAq8_m6upiqUVBeYA2q0sed0HRF4gxG3I8A,4047
|
445
|
+
omlish-0.0.0.dev81.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
446
|
+
omlish-0.0.0.dev81.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
447
|
+
omlish-0.0.0.dev81.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
448
|
+
omlish-0.0.0.dev81.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|