omdev 0.0.0.dev440__py3-none-any.whl → 0.0.0.dev495__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 omdev might be problematic. Click here for more details.
- omdev/.omlish-manifests.json +18 -30
- omdev/README.md +51 -0
- omdev/__about__.py +11 -7
- omdev/amalg/gen/gen.py +49 -6
- omdev/amalg/gen/imports.py +1 -1
- omdev/amalg/gen/manifests.py +1 -1
- omdev/amalg/gen/resources.py +1 -1
- omdev/amalg/gen/srcfiles.py +13 -3
- omdev/amalg/gen/strip.py +1 -1
- omdev/amalg/gen/types.py +1 -1
- omdev/amalg/gen/typing.py +1 -1
- omdev/amalg/info.py +32 -0
- omdev/cache/data/actions.py +1 -1
- omdev/cache/data/specs.py +1 -1
- omdev/cexts/_boilerplate.cc +2 -3
- omdev/cexts/cmake.py +4 -1
- omdev/ci/cli.py +2 -3
- omdev/cli/clicli.py +37 -7
- omdev/cmdlog/cli.py +1 -2
- omdev/dataclasses/_dumping.py +1960 -0
- omdev/dataclasses/_template.py +22 -0
- omdev/dataclasses/cli.py +7 -2
- omdev/dataclasses/codegen.py +340 -60
- omdev/dataclasses/dumping.py +200 -0
- omdev/interp/cli.py +1 -1
- omdev/interp/types.py +3 -2
- omdev/interp/uv/provider.py +37 -0
- omdev/interp/venvs.py +1 -0
- omdev/irc/messages/base.py +50 -0
- omdev/irc/messages/formats.py +92 -0
- omdev/irc/messages/messages.py +775 -0
- omdev/irc/messages/parsing.py +99 -0
- omdev/irc/numerics/__init__.py +0 -0
- omdev/irc/numerics/formats.py +97 -0
- omdev/irc/numerics/numerics.py +865 -0
- omdev/irc/numerics/types.py +59 -0
- omdev/irc/protocol/LICENSE +11 -0
- omdev/irc/protocol/__init__.py +61 -0
- omdev/irc/protocol/consts.py +6 -0
- omdev/irc/protocol/errors.py +30 -0
- omdev/irc/protocol/message.py +21 -0
- omdev/irc/protocol/nuh.py +55 -0
- omdev/irc/protocol/parsing.py +158 -0
- omdev/irc/protocol/rendering.py +153 -0
- omdev/irc/protocol/tags.py +102 -0
- omdev/irc/protocol/utils.py +30 -0
- omdev/manifests/_dumping.py +125 -25
- omdev/manifests/main.py +1 -1
- omdev/markdown/__init__.py +0 -0
- omdev/markdown/incparse.py +116 -0
- omdev/markdown/tokens.py +51 -0
- omdev/packaging/marshal.py +8 -8
- omdev/packaging/requires.py +6 -6
- omdev/packaging/revisions.py +1 -1
- omdev/packaging/specifiers.py +2 -1
- omdev/packaging/versions.py +4 -4
- omdev/packaging/wheelfile.py +2 -0
- omdev/precheck/blanklines.py +66 -0
- omdev/precheck/caches.py +1 -1
- omdev/precheck/imports.py +14 -1
- omdev/precheck/main.py +4 -3
- omdev/precheck/unicode.py +39 -15
- omdev/py/asts/__init__.py +0 -0
- omdev/py/asts/parents.py +28 -0
- omdev/py/asts/toplevel.py +123 -0
- omdev/py/asts/visitors.py +18 -0
- omdev/py/attrdocs.py +1 -1
- omdev/py/bracepy.py +12 -4
- omdev/py/reprs.py +32 -0
- omdev/py/srcheaders.py +1 -1
- omdev/py/tokens/__init__.py +0 -0
- omdev/py/tools/mkrelimp.py +1 -1
- omdev/py/tools/pipdepup.py +686 -0
- omdev/pyproject/cli.py +1 -1
- omdev/pyproject/pkg.py +190 -45
- omdev/pyproject/reqs.py +31 -9
- omdev/pyproject/tools/__init__.py +0 -0
- omdev/pyproject/tools/aboutdeps.py +60 -0
- omdev/pyproject/venvs.py +8 -1
- omdev/rs/__init__.py +0 -0
- omdev/scripts/ci.py +752 -98
- omdev/scripts/interp.py +232 -39
- omdev/scripts/lib/inject.py +74 -27
- omdev/scripts/lib/logs.py +187 -43
- omdev/scripts/lib/marshal.py +67 -25
- omdev/scripts/pyproject.py +1369 -143
- omdev/tools/git/cli.py +10 -0
- omdev/tools/json/formats.py +2 -0
- omdev/tools/json/processing.py +5 -2
- omdev/tools/jsonview/cli.py +49 -65
- omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
- omdev/tools/pawk/README.md +195 -0
- omdev/tools/pawk/pawk.py +2 -2
- omdev/tools/pip.py +8 -0
- omdev/tui/__init__.py +0 -0
- omdev/tui/apps/__init__.py +0 -0
- omdev/tui/apps/edit/__init__.py +0 -0
- omdev/tui/apps/edit/main.py +167 -0
- omdev/tui/apps/irc/__init__.py +0 -0
- omdev/tui/apps/irc/__main__.py +4 -0
- omdev/tui/apps/irc/app.py +286 -0
- omdev/tui/apps/irc/client.py +187 -0
- omdev/tui/apps/irc/commands.py +175 -0
- omdev/tui/apps/irc/main.py +26 -0
- omdev/tui/apps/markdown/__init__.py +0 -0
- omdev/tui/apps/markdown/__main__.py +11 -0
- omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
- omdev/tui/rich/__init__.py +46 -0
- omdev/tui/rich/console2.py +20 -0
- omdev/tui/rich/markdown2.py +186 -0
- omdev/tui/textual/__init__.py +265 -0
- omdev/tui/textual/app2.py +16 -0
- omdev/tui/textual/autocomplete/LICENSE +21 -0
- omdev/tui/textual/autocomplete/__init__.py +33 -0
- omdev/tui/textual/autocomplete/matching.py +226 -0
- omdev/tui/textual/autocomplete/paths.py +202 -0
- omdev/tui/textual/autocomplete/widget.py +612 -0
- omdev/tui/textual/debug/__init__.py +10 -0
- omdev/tui/textual/debug/dominfo.py +151 -0
- omdev/tui/textual/debug/screen.py +24 -0
- omdev/tui/textual/devtools.py +187 -0
- omdev/tui/textual/drivers2.py +55 -0
- omdev/tui/textual/logging2.py +20 -0
- omdev/tui/textual/types.py +45 -0
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/METADATA +15 -9
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/RECORD +135 -80
- omdev/ptk/__init__.py +0 -103
- omdev/ptk/apps/ncdu.py +0 -167
- omdev/ptk/confirm.py +0 -60
- omdev/ptk/markdown/LICENSE +0 -22
- omdev/ptk/markdown/__init__.py +0 -10
- omdev/ptk/markdown/__main__.py +0 -11
- omdev/ptk/markdown/border.py +0 -94
- omdev/ptk/markdown/markdown.py +0 -390
- omdev/ptk/markdown/parser.py +0 -42
- omdev/ptk/markdown/styles.py +0 -29
- omdev/ptk/markdown/tags.py +0 -299
- omdev/ptk/markdown/utils.py +0 -366
- omdev/pyproject/cexts.py +0 -110
- /omdev/{ptk/apps → irc}/__init__.py +0 -0
- /omdev/{tokens → irc/messages}/__init__.py +0 -0
- /omdev/{tokens → py/tokens}/all.py +0 -0
- /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
- /omdev/{tokens → py/tokens}/utils.py +0 -0
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
# ruff: noqa
|
|
3
|
+
# flake8: noqa
|
|
4
|
+
import dataclasses
|
|
5
|
+
import reprlib
|
|
6
|
+
import types
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
REGISTRY = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _register(**kwargs):
|
|
16
|
+
def inner(fn):
|
|
17
|
+
REGISTRY[kwargs['plan_repr']] = (kwargs, fn)
|
|
18
|
+
return fn
|
|
19
|
+
return inner
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
##
|
omdev/dataclasses/cli.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from omlish.argparse import all as ap
|
|
2
|
-
from omlish.logs.standard import configure_standard_logging
|
|
2
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
3
3
|
|
|
4
4
|
from .codegen import DataclassCodeGen
|
|
5
5
|
|
|
@@ -12,7 +12,12 @@ class Cli(ap.Cli):
|
|
|
12
12
|
ap.arg('roots', metavar='root', nargs='+'),
|
|
13
13
|
)
|
|
14
14
|
def codegen(self) -> None:
|
|
15
|
-
|
|
15
|
+
import asyncio
|
|
16
|
+
asyncio.run(DataclassCodeGen(
|
|
17
|
+
# dump_inline=True,
|
|
18
|
+
).run(
|
|
19
|
+
self.args.roots,
|
|
20
|
+
))
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
##
|
omdev/dataclasses/codegen.py
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
-
- subdir conf files override parents, codegen those separately, don't duplicate
|
|
4
3
|
- refactor dc gen to just Execute and Codegen
|
|
5
4
|
- need to bubble up imports, preamble, deduped
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
|
|
5
|
+
- _gen subdirs
|
|
6
|
+
- static analyze codegen kwarg if possible
|
|
7
|
+
- better ignore configurability than just tests dirs lol
|
|
9
8
|
"""
|
|
10
|
-
import
|
|
9
|
+
import ast
|
|
10
|
+
import asyncio
|
|
11
|
+
import hashlib
|
|
12
|
+
import inspect
|
|
13
|
+
import json
|
|
11
14
|
import os.path
|
|
15
|
+
import shlex
|
|
16
|
+
import sys
|
|
17
|
+
import tempfile
|
|
18
|
+
import time
|
|
12
19
|
import typing as ta
|
|
13
20
|
|
|
14
21
|
from omlish import check
|
|
15
22
|
from omlish import collections as col
|
|
23
|
+
from omlish import dataclasses as dc
|
|
16
24
|
from omlish import lang
|
|
17
|
-
from omlish.
|
|
18
|
-
from omlish.
|
|
19
|
-
from omlish.dataclasses.impl.generation.processor import Codegen as CodegenProcessingOption
|
|
20
|
-
from omlish.dataclasses.impl.generation.processor import GeneratorProcessor
|
|
21
|
-
from omlish.dataclasses.impl.processing.base import ProcessingContext
|
|
22
|
-
from omlish.dataclasses.impl.processing.driving import processing_options_context
|
|
25
|
+
from omlish.asyncs.asyncio import all as au
|
|
26
|
+
from omlish.lite.marshal import unmarshal_obj
|
|
23
27
|
from omlish.logs import all as logs
|
|
24
28
|
|
|
29
|
+
from ..py.asts.toplevel import TopLevelCall
|
|
30
|
+
from ..py.asts.toplevel import analyze_module_top_level
|
|
31
|
+
from ..py.reprs import textwrap_repr
|
|
32
|
+
from ..py.srcheaders import get_py_header_lines
|
|
33
|
+
from .dumping import DataclassCodegenDumperOutput
|
|
34
|
+
from .dumping import DumpedDataclassCodegen
|
|
35
|
+
|
|
25
36
|
|
|
26
37
|
log = logs.get_module_logger(globals())
|
|
27
38
|
|
|
@@ -29,67 +40,336 @@ log = logs.get_module_logger(globals())
|
|
|
29
40
|
##
|
|
30
41
|
|
|
31
42
|
|
|
43
|
+
def _find_dir_py_files(dir_path: str) -> list[str]:
|
|
44
|
+
return sorted(
|
|
45
|
+
os.path.join(p, fn)
|
|
46
|
+
for p, dns, fns in os.walk(dir_path)
|
|
47
|
+
for fn in fns
|
|
48
|
+
if fn.endswith('.py')
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@lang.cached_function
|
|
53
|
+
def _module_manifest_dumper_payload_src() -> str:
|
|
54
|
+
from . import _dumping
|
|
55
|
+
return inspect.getsource(_dumping)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_generated_py_file(fp: str) -> bool:
|
|
59
|
+
with open(fp) as f:
|
|
60
|
+
gen_file_src = f.read()
|
|
61
|
+
|
|
62
|
+
gen_hdr_lines = get_py_header_lines(gen_file_src)
|
|
63
|
+
return any(hl.src.strip() == '# @omlish-generated' for hl in gen_hdr_lines)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
#
|
|
67
|
+
|
|
68
|
+
|
|
32
69
|
class DataclassCodeGen:
|
|
33
|
-
|
|
70
|
+
DEFAULT_TARGET_LINE_WIDTH: ta.ClassVar[int] = 120
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
target_line_width: int | None = None,
|
|
76
|
+
dump_inline: bool = False,
|
|
77
|
+
subprocess_kwargs: ta.Mapping[str, ta.Any] | None = None,
|
|
78
|
+
) -> None:
|
|
34
79
|
super().__init__()
|
|
35
80
|
|
|
36
|
-
|
|
81
|
+
self._target_line_width = target_line_width or self.DEFAULT_TARGET_LINE_WIDTH
|
|
82
|
+
self._dump_inline = dump_inline
|
|
83
|
+
self._subprocess_kwargs = subprocess_kwargs
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
|
|
87
|
+
@dc.dataclass(frozen=True)
|
|
88
|
+
class ConfiguredPackage:
|
|
89
|
+
name: str
|
|
90
|
+
|
|
91
|
+
init_file_path: str
|
|
92
|
+
init_module_name: str
|
|
93
|
+
|
|
94
|
+
init_package_call: TopLevelCall
|
|
95
|
+
|
|
96
|
+
def scan_py_file(self, file_path: str) -> ConfiguredPackage | None:
|
|
97
|
+
with open(file_path) as f:
|
|
98
|
+
source = f.read()
|
|
99
|
+
|
|
100
|
+
module = check.isinstance(ast.parse(source), ast.Module)
|
|
101
|
+
module_name = '.'.join([
|
|
102
|
+
*os.path.dirname(file_path).split(os.path.sep),
|
|
103
|
+
os.path.basename(file_path).removesuffix('.py'),
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
tl = analyze_module_top_level(module, module_name)
|
|
107
|
+
|
|
108
|
+
init_calls: list[TopLevelCall] = []
|
|
109
|
+
for call in tl.calls:
|
|
110
|
+
if call.imp.spec != 'omlish.dataclasses':
|
|
111
|
+
continue
|
|
112
|
+
match call.node:
|
|
113
|
+
case ast.Call(func=ast.Attribute(attr='init_package')):
|
|
114
|
+
init_calls.append(call)
|
|
115
|
+
|
|
116
|
+
if not init_calls:
|
|
117
|
+
return None
|
|
118
|
+
if os.path.basename(file_path) != '__init__.py':
|
|
119
|
+
raise ValueError(f'File {file_path} has init_package call and is not an __init__.py: {init_calls!r}')
|
|
120
|
+
if len(init_calls) > 1:
|
|
121
|
+
raise ValueError(f'File {file_path} has multiple init_package calls: {init_calls!r}')
|
|
122
|
+
|
|
123
|
+
return DataclassCodeGen.ConfiguredPackage(
|
|
124
|
+
'.'.join(module_name.split('.')[:-1]),
|
|
125
|
+
file_path,
|
|
126
|
+
module_name,
|
|
127
|
+
check.single(init_calls),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def find_configured_packages(self, root_dirs: ta.Iterable[str]) -> list[ConfiguredPackage]:
|
|
131
|
+
check.not_isinstance(root_dirs, str)
|
|
132
|
+
|
|
133
|
+
py_files = (
|
|
134
|
+
fp
|
|
135
|
+
for rd in root_dirs
|
|
136
|
+
for fp in _find_dir_py_files(rd)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return [
|
|
140
|
+
cfg_pkg
|
|
141
|
+
for file_path in py_files
|
|
142
|
+
if (cfg_pkg := self.scan_py_file(file_path)) is not None
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
#
|
|
146
|
+
|
|
147
|
+
async def _run_dumper_subprocess(
|
|
37
148
|
self,
|
|
38
|
-
|
|
39
|
-
|
|
149
|
+
cfg_pkg: ConfiguredPackage,
|
|
150
|
+
dumper_kwargs: ta.Mapping[str, ta.Any],
|
|
151
|
+
*,
|
|
152
|
+
shell_wrap: bool = True,
|
|
40
153
|
) -> None:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
print(repr(e))
|
|
67
|
-
|
|
68
|
-
def build_config_trie(
|
|
154
|
+
dumper_payload_src = _module_manifest_dumper_payload_src()
|
|
155
|
+
|
|
156
|
+
subproc_src = '\n\n'.join([
|
|
157
|
+
dumper_payload_src,
|
|
158
|
+
f'_DataclassCodegenDumper()(**{dumper_kwargs!r})\n',
|
|
159
|
+
])
|
|
160
|
+
|
|
161
|
+
args = [
|
|
162
|
+
sys.executable,
|
|
163
|
+
'-c',
|
|
164
|
+
subproc_src,
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
if shell_wrap:
|
|
168
|
+
args = ['sh', '-c', ' '.join(map(shlex.quote, args))]
|
|
169
|
+
|
|
170
|
+
proc = await asyncio.create_subprocess_exec(
|
|
171
|
+
*args,
|
|
172
|
+
**(self._subprocess_kwargs or {}),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if await proc.wait():
|
|
176
|
+
raise Exception('Subprocess failed')
|
|
177
|
+
|
|
178
|
+
async def _run_dumper_inline(
|
|
69
179
|
self,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
180
|
+
cfg_pkg: ConfiguredPackage,
|
|
181
|
+
dumper_kwargs: ta.Mapping[str, ta.Any],
|
|
182
|
+
) -> None:
|
|
183
|
+
from . import dumping
|
|
184
|
+
|
|
185
|
+
dumping._DataclassCodegenDumper()(**dumper_kwargs) # noqa
|
|
186
|
+
|
|
187
|
+
async def process_configured_package(
|
|
188
|
+
self,
|
|
189
|
+
cfg_pkg: ConfiguredPackage,
|
|
190
|
+
*,
|
|
191
|
+
warn_threshold_s: float | None = 10.,
|
|
192
|
+
) -> None:
|
|
193
|
+
log.info(lambda: f'Running codegen on package: {cfg_pkg.name}')
|
|
73
194
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for dp, _, fns in os.walk(root_dir): # noqa
|
|
77
|
-
# if PACKAGE_CONFIG_FILE_NAME in fns:
|
|
78
|
-
# with open(os.path.join(dp, PACKAGE_CONFIG_FILE_NAME)) as f:
|
|
79
|
-
# config = PackageConfig.loads(f.read())
|
|
80
|
-
# pkg_parts = dp.split(os.sep)
|
|
81
|
-
# trie[pkg_parts] = config
|
|
82
|
-
pass
|
|
195
|
+
out_dir = tempfile.mkdtemp()
|
|
196
|
+
out_file_path = os.path.join(out_dir, 'output.json')
|
|
83
197
|
|
|
84
|
-
|
|
198
|
+
dumper_kwargs = dict(
|
|
199
|
+
init_file_path=cfg_pkg.init_file_path,
|
|
200
|
+
out_file_path=out_file_path,
|
|
201
|
+
)
|
|
85
202
|
|
|
86
|
-
|
|
203
|
+
start_time = time.time()
|
|
204
|
+
|
|
205
|
+
if self._dump_inline:
|
|
206
|
+
await self._run_dumper_inline(cfg_pkg, dumper_kwargs)
|
|
207
|
+
else:
|
|
208
|
+
await self._run_dumper_subprocess(cfg_pkg, dumper_kwargs)
|
|
209
|
+
|
|
210
|
+
end_time = time.time()
|
|
211
|
+
|
|
212
|
+
if warn_threshold_s is not None and (elapsed_time := (end_time - start_time)) >= warn_threshold_s:
|
|
213
|
+
log.warning('Dataclass codegen took a long time: %s, %.2f s', cfg_pkg.name, elapsed_time)
|
|
214
|
+
|
|
215
|
+
with open(out_file_path) as f: # noqa
|
|
216
|
+
out_s = f.read()
|
|
217
|
+
|
|
218
|
+
output: DataclassCodegenDumperOutput = unmarshal_obj(json.loads(out_s), DataclassCodegenDumperOutput)
|
|
219
|
+
|
|
220
|
+
await self.process_dumper_output(cfg_pkg, output)
|
|
221
|
+
|
|
222
|
+
#
|
|
223
|
+
|
|
224
|
+
PROCESS_FN_NAME: ta.ClassVar[str] = '_process_dataclass'
|
|
225
|
+
|
|
226
|
+
async def process_dumper_output(
|
|
227
|
+
self,
|
|
228
|
+
cfg_pkg: ConfiguredPackage,
|
|
229
|
+
output: DataclassCodegenDumperOutput,
|
|
230
|
+
) -> None:
|
|
231
|
+
gen_file_path = os.path.join(os.path.dirname(cfg_pkg.init_file_path), '_dataclasses.py')
|
|
232
|
+
|
|
233
|
+
if os.path.isfile(gen_file_path):
|
|
234
|
+
if not _is_generated_py_file(gen_file_path):
|
|
235
|
+
raise RuntimeError(f'Refusing to overwrite non-generated file: {gen_file_path!r}')
|
|
236
|
+
|
|
237
|
+
if not output.dumped:
|
|
238
|
+
os.unlink(gen_file_path)
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
#
|
|
242
|
+
|
|
243
|
+
lines = [
|
|
244
|
+
'# @omlish-generated',
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
from . import _template
|
|
248
|
+
lines.extend(inspect.getsource(_template).strip().split('\n'))
|
|
249
|
+
|
|
250
|
+
#
|
|
251
|
+
|
|
252
|
+
processed_modules = set(output.processed_modules)
|
|
253
|
+
|
|
254
|
+
dumped_by_plan_repr: dict[str, list[DumpedDataclassCodegen]] = {}
|
|
255
|
+
|
|
256
|
+
seen_cls_name_tups: set[tuple[str, str]] = set()
|
|
257
|
+
|
|
258
|
+
for x in output.dumped:
|
|
259
|
+
if x.cls_module not in processed_modules:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
cls_name_tup = (x.cls_module, x.cls_qualname)
|
|
263
|
+
check.not_in(cls_name_tup, seen_cls_name_tups)
|
|
264
|
+
seen_cls_name_tups.add(cls_name_tup)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
lst = dumped_by_plan_repr[x.plan_repr]
|
|
268
|
+
except KeyError:
|
|
269
|
+
dumped_by_plan_repr[x.plan_repr] = [x]
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
y = lst[0]
|
|
273
|
+
if set(x.refs) != set(y.refs):
|
|
274
|
+
raise RuntimeError(f'Mismatched refs: {x!r} != {y!r}')
|
|
275
|
+
|
|
276
|
+
lst.append(x)
|
|
277
|
+
|
|
278
|
+
#
|
|
279
|
+
|
|
280
|
+
# Sorted by first cls name for more stable diffs than say sha1
|
|
281
|
+
for grp in sorted(
|
|
282
|
+
dumped_by_plan_repr.values(),
|
|
283
|
+
key=lambda grp: sorted([(y.mod_name, y.cls_qualname) for y in grp])[0],
|
|
284
|
+
):
|
|
285
|
+
x = grp[0]
|
|
286
|
+
pr = x.plan_repr
|
|
287
|
+
pr_sha1 = hashlib.sha1(pr.encode()).hexdigest() # noqa
|
|
288
|
+
|
|
289
|
+
fn_name = f'{self.PROCESS_FN_NAME}__{pr_sha1}'
|
|
290
|
+
|
|
291
|
+
lines.extend(['', ''])
|
|
292
|
+
|
|
293
|
+
lines.append(
|
|
294
|
+
'@_register(',
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
lines.extend([
|
|
298
|
+
f' plan_repr=(',
|
|
299
|
+
*[
|
|
300
|
+
f' {prl}'
|
|
301
|
+
for prl in textwrap_repr(x.plan_repr, self._target_line_width - 8)
|
|
302
|
+
],
|
|
303
|
+
f' ),',
|
|
304
|
+
f' plan_repr_sha1={pr_sha1!r},',
|
|
305
|
+
])
|
|
306
|
+
|
|
307
|
+
op_ref_idents = [r.ident for r in x.refs if r.kind == 'op']
|
|
308
|
+
if op_ref_idents:
|
|
309
|
+
lines.extend([
|
|
310
|
+
f' op_ref_idents=(',
|
|
311
|
+
*[
|
|
312
|
+
f' {r!r},'
|
|
313
|
+
for r in sorted(op_ref_idents)
|
|
314
|
+
],
|
|
315
|
+
f' ),',
|
|
316
|
+
])
|
|
317
|
+
else:
|
|
318
|
+
lines.append(
|
|
319
|
+
' op_ref_idents=(),',
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
lines.extend([
|
|
323
|
+
f' cls_names=(',
|
|
324
|
+
*[
|
|
325
|
+
f' {cn!r},'
|
|
326
|
+
for cn in sorted([
|
|
327
|
+
(y.mod_name, y.cls_qualname)
|
|
328
|
+
for y in grp
|
|
329
|
+
])
|
|
330
|
+
],
|
|
331
|
+
f' ),',
|
|
332
|
+
])
|
|
333
|
+
|
|
334
|
+
lines.append(
|
|
335
|
+
')',
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
lines.extend([
|
|
339
|
+
f'def {fn_name}():',
|
|
340
|
+
])
|
|
341
|
+
|
|
342
|
+
lines.extend([
|
|
343
|
+
f' {l}' if l.strip() else ''
|
|
344
|
+
for l in x.fn_lines
|
|
345
|
+
])
|
|
346
|
+
|
|
347
|
+
lines.extend([
|
|
348
|
+
'',
|
|
349
|
+
f' return {x.fn_name}',
|
|
350
|
+
])
|
|
351
|
+
|
|
352
|
+
lines.append('')
|
|
353
|
+
|
|
354
|
+
with open(gen_file_path, 'w') as f: # noqa
|
|
355
|
+
f.write('\n'.join(lines))
|
|
356
|
+
|
|
357
|
+
async def run(
|
|
87
358
|
self,
|
|
88
359
|
root_dirs: ta.Iterable[str],
|
|
360
|
+
*,
|
|
361
|
+
concurrency: int | None = 4,
|
|
89
362
|
) -> None:
|
|
90
363
|
check.not_isinstance(root_dirs, str)
|
|
91
364
|
|
|
92
|
-
|
|
365
|
+
cfg_pkgs = self.find_configured_packages(root_dirs)
|
|
366
|
+
|
|
367
|
+
cfg_pkg_trie = col.Trie([ # noqa
|
|
368
|
+
(cfg_pkg.name.split('.'), cfg_pkg)
|
|
369
|
+
for cfg_pkg in cfg_pkgs
|
|
370
|
+
])
|
|
93
371
|
|
|
94
|
-
|
|
95
|
-
self.
|
|
372
|
+
await au.wait_maybe_concurrent([
|
|
373
|
+
self.process_configured_package(cfg_pkg)
|
|
374
|
+
for cfg_pkg in cfg_pkgs
|
|
375
|
+
], concurrency)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# ruff: noqa: UP006 UP007 UP037 UP045
|
|
2
|
+
# @omlish-lite
|
|
3
|
+
# @omlish-amalg _dumping.py
|
|
4
|
+
import dataclasses as dc
|
|
5
|
+
import os.path
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from omlish.lite.check import check
|
|
9
|
+
from omlish.lite.json import json_dumps_pretty
|
|
10
|
+
from omlish.lite.marshal import marshal_obj
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dc.dataclass(frozen=True)
|
|
17
|
+
class DumpedDataclassCodegen:
|
|
18
|
+
mod_name: str
|
|
19
|
+
|
|
20
|
+
cls_module: str
|
|
21
|
+
cls_qualname: str
|
|
22
|
+
|
|
23
|
+
plan_repr: str
|
|
24
|
+
|
|
25
|
+
fn_name: str
|
|
26
|
+
fn_params: ta.Sequence[str]
|
|
27
|
+
|
|
28
|
+
hdr_lines: ta.Sequence[str]
|
|
29
|
+
fn_lines: ta.Sequence[str]
|
|
30
|
+
|
|
31
|
+
@dc.dataclass(frozen=True)
|
|
32
|
+
class Ref:
|
|
33
|
+
kind: ta.Literal['op', 'global']
|
|
34
|
+
ident: str
|
|
35
|
+
|
|
36
|
+
refs: ta.Sequence[Ref]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dc.dataclass(frozen=True)
|
|
40
|
+
class DataclassCodegenDumperOutput:
|
|
41
|
+
init_file_path: str
|
|
42
|
+
out_file_path: str
|
|
43
|
+
|
|
44
|
+
processed_modules: ta.Sequence[str]
|
|
45
|
+
import_errors: ta.Mapping[str, str]
|
|
46
|
+
|
|
47
|
+
dumped: ta.Sequence[DumpedDataclassCodegen]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _DataclassCodegenDumper:
|
|
54
|
+
def __call__(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
init_file_path: str,
|
|
58
|
+
out_file_path: str,
|
|
59
|
+
) -> None:
|
|
60
|
+
from omlish.dataclasses.impl.configs import PACKAGE_CONFIG_CACHE # noqa
|
|
61
|
+
from omlish.dataclasses.impl.generation.compilation import OpCompiler # noqa
|
|
62
|
+
from omlish.dataclasses.impl.generation.globals import FnGlobal # noqa
|
|
63
|
+
from omlish.dataclasses.impl.generation.ops import OpRef # noqa
|
|
64
|
+
from omlish.dataclasses.impl.generation.processor import Codegen # noqa
|
|
65
|
+
from omlish.dataclasses.impl.generation.processor import GeneratorProcessor # noqa
|
|
66
|
+
from omlish.dataclasses.impl.processing.base import ProcessingContext # noqa
|
|
67
|
+
from omlish.dataclasses.impl.processing.driving import processing_options_context # noqa
|
|
68
|
+
|
|
69
|
+
cur_module: ta.Optional[str] = None
|
|
70
|
+
|
|
71
|
+
dumped: ta.List[DumpedDataclassCodegen] = []
|
|
72
|
+
|
|
73
|
+
def callback(
|
|
74
|
+
ctx: ProcessingContext,
|
|
75
|
+
prepared: GeneratorProcessor.Prepared,
|
|
76
|
+
comp: OpCompiler.CompileResult,
|
|
77
|
+
) -> None:
|
|
78
|
+
d_refs: ta.List[DumpedDataclassCodegen.Ref] = []
|
|
79
|
+
for ref in comp.refs:
|
|
80
|
+
if isinstance(ref, OpRef):
|
|
81
|
+
d_refs.append(DumpedDataclassCodegen.Ref(
|
|
82
|
+
kind='op',
|
|
83
|
+
ident=ref.ident(),
|
|
84
|
+
))
|
|
85
|
+
elif isinstance(ref, FnGlobal):
|
|
86
|
+
d_refs.append(DumpedDataclassCodegen.Ref(
|
|
87
|
+
kind='global',
|
|
88
|
+
ident=ref.ident,
|
|
89
|
+
))
|
|
90
|
+
else:
|
|
91
|
+
raise TypeError(ref)
|
|
92
|
+
|
|
93
|
+
dumped.append(DumpedDataclassCodegen(
|
|
94
|
+
mod_name=check.not_none(cur_module),
|
|
95
|
+
|
|
96
|
+
cls_module=ctx.cls.__module__,
|
|
97
|
+
cls_qualname=ctx.cls.__qualname__,
|
|
98
|
+
|
|
99
|
+
plan_repr=repr(prepared.plans),
|
|
100
|
+
|
|
101
|
+
fn_name=comp.fn_name,
|
|
102
|
+
fn_params=comp.fn_params,
|
|
103
|
+
|
|
104
|
+
hdr_lines=comp.hdr_lines,
|
|
105
|
+
fn_lines=comp.fn_lines,
|
|
106
|
+
|
|
107
|
+
refs=d_refs,
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
|
|
112
|
+
processed_modules: ta.List[str] = []
|
|
113
|
+
|
|
114
|
+
import_errors: ta.Dict[str, str] = {}
|
|
115
|
+
|
|
116
|
+
def process_module(spec: str) -> None:
|
|
117
|
+
nonlocal cur_module
|
|
118
|
+
check.none(cur_module)
|
|
119
|
+
cur_module = spec
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
processed_modules.append(spec)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
__import__(spec)
|
|
126
|
+
except Exception as e: # noqa
|
|
127
|
+
import_errors[spec] = repr(e)
|
|
128
|
+
|
|
129
|
+
finally:
|
|
130
|
+
cur_module = None
|
|
131
|
+
|
|
132
|
+
def process_dir(dir_path: str) -> None:
|
|
133
|
+
spec = '.'.join(dir_path.split(os.path.sep))
|
|
134
|
+
|
|
135
|
+
process_module(spec)
|
|
136
|
+
|
|
137
|
+
pkg_cfg = PACKAGE_CONFIG_CACHE.get(spec)
|
|
138
|
+
if pkg_cfg is not None and not pkg_cfg.cfg.codegen:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
#
|
|
142
|
+
|
|
143
|
+
dns: ta.List[str] = []
|
|
144
|
+
fns: ta.List[str] = []
|
|
145
|
+
for n in os.listdir(dir_path):
|
|
146
|
+
np = os.path.join(dir_path, n)
|
|
147
|
+
if os.path.isdir(np):
|
|
148
|
+
dns.append(n)
|
|
149
|
+
elif os.path.isfile(np):
|
|
150
|
+
fns.append(n)
|
|
151
|
+
|
|
152
|
+
for fn in sorted(fns):
|
|
153
|
+
if not fn.endswith('.py') or fn in ('conftest.py', '_dataclasses.py'):
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
fp = os.path.join(dir_path, fn)
|
|
157
|
+
|
|
158
|
+
fpp = fp.split(os.path.sep)
|
|
159
|
+
check.state(fpp[-1].endswith('.py'))
|
|
160
|
+
fpp[-1] = fpp[-1][:-3]
|
|
161
|
+
if fpp[-1] == '__init__':
|
|
162
|
+
fpp.pop()
|
|
163
|
+
spec = '.'.join(fpp)
|
|
164
|
+
|
|
165
|
+
process_module(spec)
|
|
166
|
+
|
|
167
|
+
for dn in sorted(dns):
|
|
168
|
+
if dn == 'tests':
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
dp = os.path.join(dir_path, dn)
|
|
172
|
+
|
|
173
|
+
if not os.path.isfile(os.path.join(dp, '__init__.py')):
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
process_dir(dp)
|
|
177
|
+
|
|
178
|
+
#
|
|
179
|
+
|
|
180
|
+
with processing_options_context(Codegen(
|
|
181
|
+
style='aot',
|
|
182
|
+
force=True,
|
|
183
|
+
callback=callback,
|
|
184
|
+
)):
|
|
185
|
+
process_dir(os.path.dirname(init_file_path))
|
|
186
|
+
|
|
187
|
+
#
|
|
188
|
+
|
|
189
|
+
output = DataclassCodegenDumperOutput(
|
|
190
|
+
init_file_path=init_file_path,
|
|
191
|
+
out_file_path=out_file_path,
|
|
192
|
+
|
|
193
|
+
processed_modules=processed_modules,
|
|
194
|
+
import_errors=import_errors,
|
|
195
|
+
|
|
196
|
+
dumped=dumped,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
with open(out_file_path, 'w') as f:
|
|
200
|
+
f.write(json_dumps_pretty(marshal_obj(output)))
|
omdev/interp/cli.py
CHANGED
|
@@ -17,7 +17,7 @@ from omlish.lite.check import check
|
|
|
17
17
|
from omlish.lite.inject import Injector
|
|
18
18
|
from omlish.lite.inject import inj
|
|
19
19
|
from omlish.lite.runtime import check_lite_runtime_version
|
|
20
|
-
from omlish.logs.standard import configure_standard_logging
|
|
20
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
21
21
|
|
|
22
22
|
from .inject import bind_interp
|
|
23
23
|
from .resolvers import InterpResolver
|
omdev/interp/types.py
CHANGED
|
@@ -85,9 +85,10 @@ class InterpSpecifier:
|
|
|
85
85
|
def parse(cls, s: str) -> 'InterpSpecifier':
|
|
86
86
|
s, o = InterpOpts.parse_suffix(s)
|
|
87
87
|
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
|
88
|
-
s = '~=' + s
|
|
89
88
|
if s.count('.') < 2:
|
|
90
|
-
s
|
|
89
|
+
s = '~=' + s + '.0'
|
|
90
|
+
else:
|
|
91
|
+
s = '==' + s
|
|
91
92
|
return cls(
|
|
92
93
|
specifier=Specifier(s),
|
|
93
94
|
opts=o,
|