omdev 0.0.0.dev486__py3-none-any.whl → 0.0.0.dev506__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 +2 -2
- omdev/README.md +51 -0
- omdev/__about__.py +4 -2
- omdev/ci/cli.py +1 -1
- omdev/cli/clicli.py +37 -7
- omdev/dataclasses/cli.py +1 -1
- omdev/interp/cli.py +1 -1
- omdev/interp/types.py +3 -2
- omdev/interp/uv/provider.py +36 -0
- omdev/manifests/main.py +1 -1
- omdev/markdown/incparse.py +392 -0
- omdev/packaging/revisions.py +1 -1
- omdev/py/tools/pipdepup.py +150 -93
- omdev/pyproject/cli.py +2 -36
- omdev/pyproject/configs.py +1 -1
- omdev/pyproject/pkg.py +1 -1
- omdev/pyproject/reqs.py +8 -7
- omdev/pyproject/tools/aboutdeps.py +5 -0
- omdev/pyproject/tools/pyversions.py +47 -0
- omdev/pyproject/versions.py +40 -0
- omdev/scripts/ci.py +369 -26
- omdev/scripts/interp.py +51 -9
- omdev/scripts/lib/inject.py +8 -1
- omdev/scripts/lib/logs.py +117 -21
- omdev/scripts/pyproject.py +479 -76
- omdev/tools/git/cli.py +43 -13
- omdev/tools/json/formats.py +2 -0
- omdev/tools/jsonview/cli.py +19 -61
- omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
- omdev/tools/pawk/README.md +195 -0
- omdev/tools/sqlrepl.py +189 -78
- omdev/tui/apps/edit/main.py +5 -1
- omdev/tui/apps/irc/app.py +28 -20
- omdev/tui/apps/irc/commands.py +1 -1
- omdev/tui/rich/__init__.py +12 -0
- omdev/tui/rich/markdown2.py +219 -18
- omdev/tui/textual/__init__.py +41 -2
- omdev/tui/textual/app2.py +6 -1
- 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/logging2.py +20 -0
- omdev/tui/textual/types.py +45 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/METADATA +10 -6
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/RECORD +50 -39
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev506.dist-info}/top_level.txt +0 -0
omdev/tui/textual/__init__.py
CHANGED
|
@@ -6,6 +6,8 @@ from omlish import lang as _lang
|
|
|
6
6
|
with _lang.auto_proxy_init(globals()):
|
|
7
7
|
##
|
|
8
8
|
|
|
9
|
+
from textual import LogGroup # noqa
|
|
10
|
+
from textual import LogVerbosity # noqa
|
|
9
11
|
from textual import app # noqa
|
|
10
12
|
from textual import binding # noqa
|
|
11
13
|
from textual import constants # noqa
|
|
@@ -78,6 +80,8 @@ with _lang.auto_proxy_init(globals()):
|
|
|
78
80
|
from textual.content import ContentType # noqa
|
|
79
81
|
from textual.content import EMPTY_CONTENT # noqa
|
|
80
82
|
from textual.content import Span # noqa
|
|
83
|
+
from textual.dom import DOMError # noqa
|
|
84
|
+
from textual.dom import DOMNode # noqa
|
|
81
85
|
from textual.driver import Driver # noqa
|
|
82
86
|
from textual.events import Action # noqa
|
|
83
87
|
from textual.events import AppBlur # noqa
|
|
@@ -210,12 +214,38 @@ with _lang.auto_proxy_init(globals()):
|
|
|
210
214
|
from textual.widgets import Tooltip # noqa
|
|
211
215
|
from textual.widgets import Tree # noqa
|
|
212
216
|
from textual.widgets import Welcome # noqa
|
|
213
|
-
from textual.widgets.
|
|
214
|
-
from textual.widgets.
|
|
217
|
+
from textual.widgets.markdown import MarkdownBlock # noqa
|
|
218
|
+
from textual.widgets.markdown import MarkdownFence # noqa
|
|
219
|
+
from textual.widgets.markdown import MarkdownStream # noqa
|
|
220
|
+
from textual.widgets.markdown import MarkdownTableOfContents # noqa
|
|
215
221
|
from textual.widgets.option_list import DuplicateID # noqa
|
|
222
|
+
from textual.widgets.option_list import Option # noqa
|
|
223
|
+
from textual.widgets.option_list import OptionDoesNotExist # noqa
|
|
216
224
|
|
|
217
225
|
##
|
|
218
226
|
|
|
227
|
+
from textual_dev.client import DevtoolsClient # noqa
|
|
228
|
+
from textual_dev.client import DevtoolsConnectionError # noqa
|
|
229
|
+
from textual_dev.client import DevtoolsConsole # noqa
|
|
230
|
+
from textual_dev.client import DevtoolsLog # noqa
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
|
|
234
|
+
from . devtools import ( # noqa
|
|
235
|
+
DevtoolsConfig,
|
|
236
|
+
connect_devtools,
|
|
237
|
+
|
|
238
|
+
DevtoolsAppMixin,
|
|
239
|
+
|
|
240
|
+
DevtoolsSetup,
|
|
241
|
+
DevtoolsManager,
|
|
242
|
+
|
|
243
|
+
DevtoolsLoggingHandler,
|
|
244
|
+
set_root_logger_to_devtools,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
from . import debug # noqa
|
|
248
|
+
|
|
219
249
|
from .app2 import ( # noqa
|
|
220
250
|
App,
|
|
221
251
|
)
|
|
@@ -224,3 +254,12 @@ with _lang.auto_proxy_init(globals()):
|
|
|
224
254
|
PendingWritesDriverMixin,
|
|
225
255
|
get_pending_writes_driver_class,
|
|
226
256
|
)
|
|
257
|
+
|
|
258
|
+
from .logging2 import ( # noqa
|
|
259
|
+
translate_log_level,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
from .types import ( # noqa
|
|
263
|
+
TopRightBottomLeft,
|
|
264
|
+
trbl_to_dict,
|
|
265
|
+
)
|
omdev/tui/textual/app2.py
CHANGED
|
@@ -3,9 +3,14 @@ import typing as ta
|
|
|
3
3
|
from textual.app import App as App_
|
|
4
4
|
from textual.binding import BindingType # noqa
|
|
5
5
|
|
|
6
|
+
from .devtools import DevtoolsAppMixin
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
##
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class App(
|
|
12
|
+
class App(
|
|
13
|
+
DevtoolsAppMixin,
|
|
14
|
+
App_,
|
|
15
|
+
):
|
|
11
16
|
BINDINGS: ta.ClassVar[ta.Sequence[BindingType]] = App_.BINDINGS # type: ignore[assignment]
|
|
@@ -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,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.dev506
|
|
4
4
|
Summary: omdev
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,9 +14,9 @@ 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.dev506
|
|
18
18
|
Provides-Extra: all
|
|
19
|
-
Requires-Dist: black~=
|
|
19
|
+
Requires-Dist: black~=26.1; 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"
|
|
@@ -27,9 +27,11 @@ Requires-Dist: mypy~=1.19; extra == "all"
|
|
|
27
27
|
Requires-Dist: gprof2dot~=2025.4; extra == "all"
|
|
28
28
|
Requires-Dist: segno~=1.6; extra == "all"
|
|
29
29
|
Requires-Dist: rich~=14.2; extra == "all"
|
|
30
|
-
Requires-Dist: textual~=
|
|
30
|
+
Requires-Dist: textual~=7.3; extra == "all"
|
|
31
|
+
Requires-Dist: textual-dev~=1.8; extra == "all"
|
|
32
|
+
Requires-Dist: textual-speedups~=0.2; extra == "all"
|
|
31
33
|
Provides-Extra: black
|
|
32
|
-
Requires-Dist: black~=
|
|
34
|
+
Requires-Dist: black~=26.1; extra == "black"
|
|
33
35
|
Provides-Extra: c
|
|
34
36
|
Requires-Dist: pycparser~=2.23; extra == "c"
|
|
35
37
|
Requires-Dist: pcpp~=1.30; extra == "c"
|
|
@@ -46,7 +48,9 @@ Provides-Extra: qr
|
|
|
46
48
|
Requires-Dist: segno~=1.6; extra == "qr"
|
|
47
49
|
Provides-Extra: tui
|
|
48
50
|
Requires-Dist: rich~=14.2; extra == "tui"
|
|
49
|
-
Requires-Dist: textual~=
|
|
51
|
+
Requires-Dist: textual~=7.3; extra == "tui"
|
|
52
|
+
Requires-Dist: textual-dev~=1.8; extra == "tui"
|
|
53
|
+
Requires-Dist: textual-speedups~=0.2; extra == "tui"
|
|
50
54
|
Dynamic: license-file
|
|
51
55
|
|
|
52
56
|
# Overview
|