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,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- walk textual.styles.RulesMap lol
|
|
4
|
+
"""
|
|
5
|
+
import typing as ta
|
|
6
|
+
|
|
7
|
+
from textual.dom import DOMNode
|
|
8
|
+
from textual.widget import Widget
|
|
9
|
+
|
|
10
|
+
from omlish import dataclasses as dc
|
|
11
|
+
|
|
12
|
+
from ..types import trbl_to_dict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dc.dataclass()
|
|
19
|
+
class DomNodeInfo:
|
|
20
|
+
"""Representation of a Textual DOM node for debugging."""
|
|
21
|
+
|
|
22
|
+
# Identity
|
|
23
|
+
oid: int
|
|
24
|
+
oidx: str
|
|
25
|
+
type: str
|
|
26
|
+
dom_id: str | None
|
|
27
|
+
|
|
28
|
+
# CSS / Styling
|
|
29
|
+
classes: list[str]
|
|
30
|
+
pseudo_classes: list[str]
|
|
31
|
+
# 'styles' contains the computed values relevant to layout (margin, padding, etc)
|
|
32
|
+
styles: dict[str, ta.Any]
|
|
33
|
+
|
|
34
|
+
# Geometry
|
|
35
|
+
# Region: The actual screen space allocated (x, y, w, h)
|
|
36
|
+
region: dict[str, int] | None
|
|
37
|
+
# Virtual Size: The size the widget 'wants' to be (scrollable area)
|
|
38
|
+
virtual_size: dict[str, int] | None
|
|
39
|
+
# Content Size: The size of the content inside the padding
|
|
40
|
+
content_size: dict[str, int] | None
|
|
41
|
+
|
|
42
|
+
# Hierarchy
|
|
43
|
+
children: list['DomNodeInfo'] = dc.field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def inspect_dom_node(node: DOMNode) -> DomNodeInfo:
|
|
47
|
+
"""
|
|
48
|
+
Recursively builds a tree of DomNodeInfo objects from a Textual DOM node.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
node: The root node to inspect (usually app.screen or a specific widget).
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
DomNodeInfo: The root of the inspected tree.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# 1. Identity
|
|
58
|
+
|
|
59
|
+
oid = id(node)
|
|
60
|
+
oidx = f'{id(node):x}'
|
|
61
|
+
node_type = f'{type(node).__module__}.{type(node).__qualname__}'
|
|
62
|
+
dom_id = node.id
|
|
63
|
+
|
|
64
|
+
# 2. Styles
|
|
65
|
+
|
|
66
|
+
# We extract specific computed styles relevant to positioning debugging. Textual 'styles' property usually returns
|
|
67
|
+
# the computed/effective style.
|
|
68
|
+
styles_info: dict[str, ta.Any] = {}
|
|
69
|
+
|
|
70
|
+
# Extract only if the node supports styles (Most DOMNodes are Widgets, but check just in case)
|
|
71
|
+
if isinstance(node, Widget):
|
|
72
|
+
s = node.styles
|
|
73
|
+
|
|
74
|
+
# Layout Rules
|
|
75
|
+
styles_info['display'] = str(s.display)
|
|
76
|
+
styles_info['position'] = str(s.position) # relative, absolute
|
|
77
|
+
styles_info['dock'] = str(s.dock)
|
|
78
|
+
styles_info['layer'] = s.layer
|
|
79
|
+
|
|
80
|
+
# Sizing Rules (The 'constraints' set in CSS)
|
|
81
|
+
styles_info['width_rule'] = str(s.width) # e.g. '100%', 'auto', '10'
|
|
82
|
+
styles_info['height_rule'] = str(s.height)
|
|
83
|
+
styles_info['min_width'] = str(s.min_width)
|
|
84
|
+
styles_info['max_width'] = str(s.max_width)
|
|
85
|
+
styles_info['box_sizing'] = str(s.box_sizing) # border-box vs content-box
|
|
86
|
+
|
|
87
|
+
# Spacing (Critical for debugging gaps)
|
|
88
|
+
styles_info['margin'] = trbl_to_dict(s.margin)
|
|
89
|
+
styles_info['padding'] = trbl_to_dict(s.padding)
|
|
90
|
+
styles_info['border'] = trbl_to_dict(s.border)
|
|
91
|
+
|
|
92
|
+
# Alignment
|
|
93
|
+
styles_info['align'] = f'{s.align_horizontal} {s.align_vertical}'
|
|
94
|
+
|
|
95
|
+
# 3. Geometry
|
|
96
|
+
|
|
97
|
+
# Region is the absolute coordinates on the screen (or relative to parent layer). This is "What it was forced into".
|
|
98
|
+
region_info: dict[str, int] | None = None
|
|
99
|
+
if (region := getattr(node, 'region', None)) is not None:
|
|
100
|
+
region_info = {
|
|
101
|
+
'x': region.x,
|
|
102
|
+
'y': region.y,
|
|
103
|
+
'width': region.width,
|
|
104
|
+
'height': region.height,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Virtual size is the scrollable area. This is often "What it wants to be" (if larger than region).
|
|
108
|
+
virtual_info: dict[str, int] | None = None
|
|
109
|
+
if (virtual_size := getattr(node, 'virtual_size', None)) is not None:
|
|
110
|
+
virtual_info = {
|
|
111
|
+
'width': virtual_size.width,
|
|
112
|
+
'height': virtual_size.height,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
content_info: dict | None = None
|
|
116
|
+
if isinstance(node, Widget):
|
|
117
|
+
# Content region is inner size (region - padding - border)
|
|
118
|
+
content_info = {
|
|
119
|
+
'width': node.content_region.width,
|
|
120
|
+
'height': node.content_region.height,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# 4. Recursion & Sorting
|
|
124
|
+
|
|
125
|
+
# Sort by Y position first, then X position
|
|
126
|
+
sorted_children = sorted(
|
|
127
|
+
node.children,
|
|
128
|
+
key=lambda n: (n.region.y, n.region.x),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
child_nodes = [
|
|
132
|
+
inspect_dom_node(child)
|
|
133
|
+
for child in sorted_children
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
return DomNodeInfo(
|
|
137
|
+
oid=oid,
|
|
138
|
+
oidx=oidx,
|
|
139
|
+
type=node_type,
|
|
140
|
+
dom_id=dom_id,
|
|
141
|
+
|
|
142
|
+
classes=list(node.classes),
|
|
143
|
+
pseudo_classes=list(node.pseudo_classes),
|
|
144
|
+
styles=styles_info,
|
|
145
|
+
|
|
146
|
+
region=region_info,
|
|
147
|
+
virtual_size=virtual_info,
|
|
148
|
+
content_size=content_info,
|
|
149
|
+
|
|
150
|
+
children=child_nodes,
|
|
151
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from textual.geometry import Region
|
|
2
|
+
from textual.screen import Screen
|
|
3
|
+
from textual.widget import Widget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_screen_zbuffer(screen: Screen) -> list[list[list[tuple[Widget, Region]] | None]]:
|
|
10
|
+
width, height = screen.size
|
|
11
|
+
|
|
12
|
+
zbuffer: list[list[list[tuple[Widget, Region]] | None]] = [
|
|
13
|
+
[None for _ in range(width)]
|
|
14
|
+
for _ in range(height)
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
for y in range(height):
|
|
18
|
+
for x in range(width):
|
|
19
|
+
try:
|
|
20
|
+
zbuffer[y][x] = list(screen.get_widgets_at(x, y))
|
|
21
|
+
except Exception: # noqa
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
return zbuffer
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# ruff: noqa: UP037 UP045
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
import textual.constants
|
|
7
|
+
|
|
8
|
+
from omlish import check
|
|
9
|
+
from omlish import dataclasses as dc
|
|
10
|
+
from omlish import lang
|
|
11
|
+
|
|
12
|
+
from .logging2 import translate_log_level
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
with lang.auto_proxy_import(globals()):
|
|
16
|
+
from textual_dev import client as tx_dev_client
|
|
17
|
+
from textual_dev import redirect_output as tx_dev_redirect_output
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
24
|
+
class DevtoolsConfig:
|
|
25
|
+
host: str = '127.0.0.1'
|
|
26
|
+
|
|
27
|
+
# https://github.com/Textualize/textual/blob/676045381b7178c3bc94b86901f20764e08aca49/src/textual/constants.py#L125
|
|
28
|
+
port: int = 8081
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_env(cls) -> 'DevtoolsConfig':
|
|
32
|
+
return cls(
|
|
33
|
+
host=textual.constants.DEVTOOLS_HOST,
|
|
34
|
+
port=textual.constants.DEVTOOLS_PORT,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def connect_devtools(config: DevtoolsConfig) -> ta.Optional['tx_dev_client.DevtoolsClient']:
|
|
39
|
+
try:
|
|
40
|
+
from textual_dev.client import DevtoolsClient # noqa
|
|
41
|
+
except ImportError:
|
|
42
|
+
# Dev dependencies not installed
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
devtools = DevtoolsClient(
|
|
46
|
+
config.host,
|
|
47
|
+
config.port,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from textual_dev.client import DevtoolsConnectionError
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
await devtools.connect()
|
|
54
|
+
except DevtoolsConnectionError as e: # noqa
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
return devtools
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DevtoolsAppMixin:
|
|
64
|
+
_skip_devtools_management: bool = False
|
|
65
|
+
|
|
66
|
+
def _install_devtools(self, devtools: 'tx_dev_client.DevtoolsClient') -> None:
|
|
67
|
+
check.none(getattr(self, 'devtools', None))
|
|
68
|
+
check.none(getattr(self, '_devtools_redirector', None))
|
|
69
|
+
|
|
70
|
+
# https://github.com/Textualize/textual/blob/676045381b7178c3bc94b86901f20764e08aca49/src/textual/app.py#L730-L741
|
|
71
|
+
setattr(self, 'devtools', devtools)
|
|
72
|
+
setattr(self, '_devtools_redirector', tx_dev_redirect_output.StdoutRedirector(devtools))
|
|
73
|
+
|
|
74
|
+
self._skip_devtools_management = True
|
|
75
|
+
|
|
76
|
+
async def _init_devtools(self) -> None:
|
|
77
|
+
if self._skip_devtools_management:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
await super()._init_devtools() # type: ignore # noqa
|
|
81
|
+
|
|
82
|
+
async def _disconnect_devtools(self) -> None:
|
|
83
|
+
if self._skip_devtools_management:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
await super()._disconnect_devtools() # type: ignore # noqa
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class DevtoolsSetup(lang.Func1[DevtoolsAppMixin, None]):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class DevtoolsManager:
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
config: DevtoolsConfig = DevtoolsConfig(),
|
|
100
|
+
) -> None:
|
|
101
|
+
super().__init__()
|
|
102
|
+
|
|
103
|
+
self._config = config
|
|
104
|
+
self._devtools: ta.Optional['tx_dev_client.DevtoolsClient'] = None
|
|
105
|
+
self._setup: DevtoolsSetup | None = None
|
|
106
|
+
|
|
107
|
+
async def get_setup(self) -> DevtoolsSetup:
|
|
108
|
+
if self._setup is None:
|
|
109
|
+
check.none(self._devtools)
|
|
110
|
+
|
|
111
|
+
self._devtools = await connect_devtools(self._config)
|
|
112
|
+
|
|
113
|
+
self._setup = DevtoolsSetup(self._setup_app_dev_tools)
|
|
114
|
+
|
|
115
|
+
return self._setup
|
|
116
|
+
|
|
117
|
+
def _setup_app_dev_tools(self, app: DevtoolsAppMixin) -> None:
|
|
118
|
+
if (devtools := self._devtools) is None:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
check.isinstance(app, DevtoolsAppMixin)._install_devtools(devtools) # noqa
|
|
122
|
+
|
|
123
|
+
async def aclose(self) -> None:
|
|
124
|
+
if (devtools := self._devtools) is not None and devtools.is_connected:
|
|
125
|
+
await devtools.disconnect()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class DevtoolsLoggingHandler(logging.Handler):
|
|
132
|
+
"""
|
|
133
|
+
TODO:
|
|
134
|
+
- reify caller from LogContextInfos
|
|
135
|
+
- queue worker, this blocks the asyncio thread lol
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
devtools: ta.Optional['tx_dev_client.DevtoolsClient'],
|
|
141
|
+
prototype_handler: logging.Handler | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
super().__init__()
|
|
144
|
+
|
|
145
|
+
self._devtools = devtools
|
|
146
|
+
|
|
147
|
+
if prototype_handler is not None:
|
|
148
|
+
self.setFormatter(prototype_handler.formatter)
|
|
149
|
+
for lf in prototype_handler.filters:
|
|
150
|
+
self.addFilter(lf)
|
|
151
|
+
|
|
152
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
153
|
+
if (devtools := self._devtools) is None or not devtools.is_connected:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
msg = self.format(record)
|
|
157
|
+
|
|
158
|
+
caller = inspect.Traceback(
|
|
159
|
+
filename=record.filename,
|
|
160
|
+
lineno=record.lineno,
|
|
161
|
+
function=record.funcName,
|
|
162
|
+
code_context=None,
|
|
163
|
+
index=None,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
group, verbosity = translate_log_level(record.levelno)
|
|
167
|
+
|
|
168
|
+
devtools.log(
|
|
169
|
+
tx_dev_client.DevtoolsLog(
|
|
170
|
+
msg,
|
|
171
|
+
caller=caller,
|
|
172
|
+
),
|
|
173
|
+
group=group,
|
|
174
|
+
verbosity=verbosity,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def set_root_logger_to_devtools(devtools: ta.Optional['tx_dev_client.DevtoolsClient']) -> None:
|
|
179
|
+
from omlish.logs.std.standard import _locking_logging_module_lock # noqa
|
|
180
|
+
from omlish.logs.std.standard import StandardConfiguredLoggingHandler
|
|
181
|
+
|
|
182
|
+
with _locking_logging_module_lock():
|
|
183
|
+
std_handler = next((h for h in logging.root.handlers if isinstance(h, StandardConfiguredLoggingHandler)), None)
|
|
184
|
+
|
|
185
|
+
dt_handler = DevtoolsLoggingHandler(devtools, std_handler)
|
|
186
|
+
|
|
187
|
+
logging.root.handlers = [dt_handler]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import typing as ta
|
|
3
|
+
|
|
4
|
+
from omlish import lang
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if ta.TYPE_CHECKING:
|
|
8
|
+
from textual.driver import Driver
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PendingWritesDriverMixin:
|
|
15
|
+
def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
|
|
16
|
+
super().__init__(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
self._pending_primary_buffer_writes: list[str] = []
|
|
19
|
+
|
|
20
|
+
def queue_primary_buffer_write(self, *s: str) -> None:
|
|
21
|
+
self._pending_primary_buffer_writes.extend(s)
|
|
22
|
+
|
|
23
|
+
def write(self, data: str) -> None:
|
|
24
|
+
if (pw := self._pending_primary_buffer_writes):
|
|
25
|
+
data = ''.join([*pw, data])
|
|
26
|
+
pw.clear()
|
|
27
|
+
super().write(data) # type: ignore
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_PENDING_WRITES_DRIVER_CLASSES_LOCK = threading.RLock()
|
|
31
|
+
_PENDING_WRITES_DRIVER_CLASSES: dict[type['Driver'], type['Driver']] = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_pending_writes_driver_class(cls: type['Driver']) -> type['Driver']:
|
|
35
|
+
if issubclass(cls, PendingWritesDriverMixin):
|
|
36
|
+
return cls # noqa
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
return _PENDING_WRITES_DRIVER_CLASSES[cls]
|
|
40
|
+
except KeyError:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
with _PENDING_WRITES_DRIVER_CLASSES_LOCK:
|
|
44
|
+
try:
|
|
45
|
+
return _PENDING_WRITES_DRIVER_CLASSES[cls]
|
|
46
|
+
except KeyError:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
cls = _PENDING_WRITES_DRIVER_CLASSES[cls] = lang.new_type( # noqa
|
|
50
|
+
f'PendingWrites{cls.__name__}',
|
|
51
|
+
(PendingWritesDriverMixin, cls),
|
|
52
|
+
{},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return cls # noqa
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from textual import LogGroup # noqa
|
|
4
|
+
from textual import LogVerbosity # noqa
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def translate_log_level(level: int) -> tuple[LogGroup, LogVerbosity]:
|
|
11
|
+
if level >= logging.ERROR:
|
|
12
|
+
return (LogGroup.ERROR, LogVerbosity.HIGH)
|
|
13
|
+
elif level >= logging.WARNING:
|
|
14
|
+
return (LogGroup.ERROR, LogVerbosity.HIGH)
|
|
15
|
+
elif level >= logging.INFO:
|
|
16
|
+
return (LogGroup.INFO, LogVerbosity.NORMAL)
|
|
17
|
+
elif level >= logging.DEBUG:
|
|
18
|
+
return (LogGroup.DEBUG, LogVerbosity.NORMAL)
|
|
19
|
+
else:
|
|
20
|
+
return (LogGroup.UNDEFINED, LogVerbosity.NORMAL)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from textual.geometry import Spacing
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TopRightBottomLeft(ta.Protocol):
|
|
10
|
+
"""
|
|
11
|
+
A ducktype for at least the following:
|
|
12
|
+
- textual.geometry.Spacing - margin, padding
|
|
13
|
+
- textual.css._style_properties.Edges - border
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def top(self) -> ta.Any: ...
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def right(self) -> ta.Any: ...
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def bottom(self) -> ta.Any: ...
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def left(self) -> ta.Any: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@ta.overload
|
|
30
|
+
def trbl_to_dict(trbl: Spacing) -> dict[str, int]:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@ta.overload
|
|
35
|
+
def trbl_to_dict(trbl: TopRightBottomLeft) -> dict[str, ta.Any]:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def trbl_to_dict(trbl):
|
|
40
|
+
return {
|
|
41
|
+
'top': trbl.top,
|
|
42
|
+
'right': trbl.right,
|
|
43
|
+
'bottom': trbl.bottom,
|
|
44
|
+
'left': trbl.left,
|
|
45
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omdev
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev495
|
|
4
4
|
Summary: omdev
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,21 +14,24 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Requires-Python: >=3.13
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: omlish==0.0.0.
|
|
17
|
+
Requires-Dist: omlish==0.0.0.dev495
|
|
18
18
|
Provides-Extra: all
|
|
19
|
-
Requires-Dist: black~=25.
|
|
19
|
+
Requires-Dist: black~=25.12; extra == "all"
|
|
20
20
|
Requires-Dist: pycparser~=2.23; extra == "all"
|
|
21
21
|
Requires-Dist: pcpp~=1.30; extra == "all"
|
|
22
22
|
Requires-Dist: docutils~=0.22; extra == "all"
|
|
23
23
|
Requires-Dist: markdown-it-py~=4.0; extra == "all"
|
|
24
24
|
Requires-Dist: mdit-py-plugins~=0.5; extra == "all"
|
|
25
25
|
Requires-Dist: pygments~=2.19; extra == "all"
|
|
26
|
-
Requires-Dist: mypy~=1.
|
|
26
|
+
Requires-Dist: mypy~=1.19; extra == "all"
|
|
27
27
|
Requires-Dist: gprof2dot~=2025.4; extra == "all"
|
|
28
|
-
Requires-Dist: prompt-toolkit~=3.0; extra == "all"
|
|
29
28
|
Requires-Dist: segno~=1.6; extra == "all"
|
|
29
|
+
Requires-Dist: rich~=14.2; extra == "all"
|
|
30
|
+
Requires-Dist: textual~=6.11; extra == "all"
|
|
31
|
+
Requires-Dist: textual-dev~=1.8; extra == "all"
|
|
32
|
+
Requires-Dist: textual-speedups~=0.2; extra == "all"
|
|
30
33
|
Provides-Extra: black
|
|
31
|
-
Requires-Dist: black~=25.
|
|
34
|
+
Requires-Dist: black~=25.12; extra == "black"
|
|
32
35
|
Provides-Extra: c
|
|
33
36
|
Requires-Dist: pycparser~=2.23; extra == "c"
|
|
34
37
|
Requires-Dist: pcpp~=1.30; extra == "c"
|
|
@@ -38,13 +41,16 @@ Requires-Dist: markdown-it-py~=4.0; extra == "doc"
|
|
|
38
41
|
Requires-Dist: mdit-py-plugins~=0.5; extra == "doc"
|
|
39
42
|
Requires-Dist: pygments~=2.19; extra == "doc"
|
|
40
43
|
Provides-Extra: mypy
|
|
41
|
-
Requires-Dist: mypy~=1.
|
|
44
|
+
Requires-Dist: mypy~=1.19; extra == "mypy"
|
|
42
45
|
Provides-Extra: prof
|
|
43
46
|
Requires-Dist: gprof2dot~=2025.4; extra == "prof"
|
|
44
|
-
Provides-Extra: ptk
|
|
45
|
-
Requires-Dist: prompt-toolkit~=3.0; extra == "ptk"
|
|
46
47
|
Provides-Extra: qr
|
|
47
48
|
Requires-Dist: segno~=1.6; extra == "qr"
|
|
49
|
+
Provides-Extra: tui
|
|
50
|
+
Requires-Dist: rich~=14.2; extra == "tui"
|
|
51
|
+
Requires-Dist: textual~=6.11; extra == "tui"
|
|
52
|
+
Requires-Dist: textual-dev~=1.8; extra == "tui"
|
|
53
|
+
Requires-Dist: textual-speedups~=0.2; extra == "tui"
|
|
48
54
|
Dynamic: license-file
|
|
49
55
|
|
|
50
56
|
# Overview
|