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.

Files changed (148) hide show
  1. omdev/.omlish-manifests.json +18 -30
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +11 -7
  4. omdev/amalg/gen/gen.py +49 -6
  5. omdev/amalg/gen/imports.py +1 -1
  6. omdev/amalg/gen/manifests.py +1 -1
  7. omdev/amalg/gen/resources.py +1 -1
  8. omdev/amalg/gen/srcfiles.py +13 -3
  9. omdev/amalg/gen/strip.py +1 -1
  10. omdev/amalg/gen/types.py +1 -1
  11. omdev/amalg/gen/typing.py +1 -1
  12. omdev/amalg/info.py +32 -0
  13. omdev/cache/data/actions.py +1 -1
  14. omdev/cache/data/specs.py +1 -1
  15. omdev/cexts/_boilerplate.cc +2 -3
  16. omdev/cexts/cmake.py +4 -1
  17. omdev/ci/cli.py +2 -3
  18. omdev/cli/clicli.py +37 -7
  19. omdev/cmdlog/cli.py +1 -2
  20. omdev/dataclasses/_dumping.py +1960 -0
  21. omdev/dataclasses/_template.py +22 -0
  22. omdev/dataclasses/cli.py +7 -2
  23. omdev/dataclasses/codegen.py +340 -60
  24. omdev/dataclasses/dumping.py +200 -0
  25. omdev/interp/cli.py +1 -1
  26. omdev/interp/types.py +3 -2
  27. omdev/interp/uv/provider.py +37 -0
  28. omdev/interp/venvs.py +1 -0
  29. omdev/irc/messages/base.py +50 -0
  30. omdev/irc/messages/formats.py +92 -0
  31. omdev/irc/messages/messages.py +775 -0
  32. omdev/irc/messages/parsing.py +99 -0
  33. omdev/irc/numerics/__init__.py +0 -0
  34. omdev/irc/numerics/formats.py +97 -0
  35. omdev/irc/numerics/numerics.py +865 -0
  36. omdev/irc/numerics/types.py +59 -0
  37. omdev/irc/protocol/LICENSE +11 -0
  38. omdev/irc/protocol/__init__.py +61 -0
  39. omdev/irc/protocol/consts.py +6 -0
  40. omdev/irc/protocol/errors.py +30 -0
  41. omdev/irc/protocol/message.py +21 -0
  42. omdev/irc/protocol/nuh.py +55 -0
  43. omdev/irc/protocol/parsing.py +158 -0
  44. omdev/irc/protocol/rendering.py +153 -0
  45. omdev/irc/protocol/tags.py +102 -0
  46. omdev/irc/protocol/utils.py +30 -0
  47. omdev/manifests/_dumping.py +125 -25
  48. omdev/manifests/main.py +1 -1
  49. omdev/markdown/__init__.py +0 -0
  50. omdev/markdown/incparse.py +116 -0
  51. omdev/markdown/tokens.py +51 -0
  52. omdev/packaging/marshal.py +8 -8
  53. omdev/packaging/requires.py +6 -6
  54. omdev/packaging/revisions.py +1 -1
  55. omdev/packaging/specifiers.py +2 -1
  56. omdev/packaging/versions.py +4 -4
  57. omdev/packaging/wheelfile.py +2 -0
  58. omdev/precheck/blanklines.py +66 -0
  59. omdev/precheck/caches.py +1 -1
  60. omdev/precheck/imports.py +14 -1
  61. omdev/precheck/main.py +4 -3
  62. omdev/precheck/unicode.py +39 -15
  63. omdev/py/asts/__init__.py +0 -0
  64. omdev/py/asts/parents.py +28 -0
  65. omdev/py/asts/toplevel.py +123 -0
  66. omdev/py/asts/visitors.py +18 -0
  67. omdev/py/attrdocs.py +1 -1
  68. omdev/py/bracepy.py +12 -4
  69. omdev/py/reprs.py +32 -0
  70. omdev/py/srcheaders.py +1 -1
  71. omdev/py/tokens/__init__.py +0 -0
  72. omdev/py/tools/mkrelimp.py +1 -1
  73. omdev/py/tools/pipdepup.py +686 -0
  74. omdev/pyproject/cli.py +1 -1
  75. omdev/pyproject/pkg.py +190 -45
  76. omdev/pyproject/reqs.py +31 -9
  77. omdev/pyproject/tools/__init__.py +0 -0
  78. omdev/pyproject/tools/aboutdeps.py +60 -0
  79. omdev/pyproject/venvs.py +8 -1
  80. omdev/rs/__init__.py +0 -0
  81. omdev/scripts/ci.py +752 -98
  82. omdev/scripts/interp.py +232 -39
  83. omdev/scripts/lib/inject.py +74 -27
  84. omdev/scripts/lib/logs.py +187 -43
  85. omdev/scripts/lib/marshal.py +67 -25
  86. omdev/scripts/pyproject.py +1369 -143
  87. omdev/tools/git/cli.py +10 -0
  88. omdev/tools/json/formats.py +2 -0
  89. omdev/tools/json/processing.py +5 -2
  90. omdev/tools/jsonview/cli.py +49 -65
  91. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  92. omdev/tools/pawk/README.md +195 -0
  93. omdev/tools/pawk/pawk.py +2 -2
  94. omdev/tools/pip.py +8 -0
  95. omdev/tui/__init__.py +0 -0
  96. omdev/tui/apps/__init__.py +0 -0
  97. omdev/tui/apps/edit/__init__.py +0 -0
  98. omdev/tui/apps/edit/main.py +167 -0
  99. omdev/tui/apps/irc/__init__.py +0 -0
  100. omdev/tui/apps/irc/__main__.py +4 -0
  101. omdev/tui/apps/irc/app.py +286 -0
  102. omdev/tui/apps/irc/client.py +187 -0
  103. omdev/tui/apps/irc/commands.py +175 -0
  104. omdev/tui/apps/irc/main.py +26 -0
  105. omdev/tui/apps/markdown/__init__.py +0 -0
  106. omdev/tui/apps/markdown/__main__.py +11 -0
  107. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  108. omdev/tui/rich/__init__.py +46 -0
  109. omdev/tui/rich/console2.py +20 -0
  110. omdev/tui/rich/markdown2.py +186 -0
  111. omdev/tui/textual/__init__.py +265 -0
  112. omdev/tui/textual/app2.py +16 -0
  113. omdev/tui/textual/autocomplete/LICENSE +21 -0
  114. omdev/tui/textual/autocomplete/__init__.py +33 -0
  115. omdev/tui/textual/autocomplete/matching.py +226 -0
  116. omdev/tui/textual/autocomplete/paths.py +202 -0
  117. omdev/tui/textual/autocomplete/widget.py +612 -0
  118. omdev/tui/textual/debug/__init__.py +10 -0
  119. omdev/tui/textual/debug/dominfo.py +151 -0
  120. omdev/tui/textual/debug/screen.py +24 -0
  121. omdev/tui/textual/devtools.py +187 -0
  122. omdev/tui/textual/drivers2.py +55 -0
  123. omdev/tui/textual/logging2.py +20 -0
  124. omdev/tui/textual/types.py +45 -0
  125. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/METADATA +15 -9
  126. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/RECORD +135 -80
  127. omdev/ptk/__init__.py +0 -103
  128. omdev/ptk/apps/ncdu.py +0 -167
  129. omdev/ptk/confirm.py +0 -60
  130. omdev/ptk/markdown/LICENSE +0 -22
  131. omdev/ptk/markdown/__init__.py +0 -10
  132. omdev/ptk/markdown/__main__.py +0 -11
  133. omdev/ptk/markdown/border.py +0 -94
  134. omdev/ptk/markdown/markdown.py +0 -390
  135. omdev/ptk/markdown/parser.py +0 -42
  136. omdev/ptk/markdown/styles.py +0 -29
  137. omdev/ptk/markdown/tags.py +0 -299
  138. omdev/ptk/markdown/utils.py +0 -366
  139. omdev/pyproject/cexts.py +0 -110
  140. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  141. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  142. /omdev/{tokens → py/tokens}/all.py +0 -0
  143. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  144. /omdev/{tokens → py/tokens}/utils.py +0 -0
  145. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/WHEEL +0 -0
  146. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/entry_points.txt +0 -0
  147. {omdev-0.0.0.dev440.dist-info → omdev-0.0.0.dev495.dist-info}/licenses/LICENSE +0 -0
  148. {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.dev440
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.dev440
17
+ Requires-Dist: omlish==0.0.0.dev495
18
18
  Provides-Extra: all
19
- Requires-Dist: black~=25.1; extra == "all"
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.18; extra == "all"
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.1; extra == "black"
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.18; extra == "mypy"
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