omdev 0.0.0.dev486__py3-none-any.whl → 0.0.0.dev500__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 (43) hide show
  1. omdev/.omlish-manifests.json +1 -1
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +4 -2
  4. omdev/ci/cli.py +1 -1
  5. omdev/cli/clicli.py +37 -7
  6. omdev/dataclasses/cli.py +1 -1
  7. omdev/interp/cli.py +1 -1
  8. omdev/interp/types.py +3 -2
  9. omdev/interp/uv/provider.py +36 -0
  10. omdev/manifests/main.py +1 -1
  11. omdev/packaging/revisions.py +1 -1
  12. omdev/py/tools/pipdepup.py +150 -93
  13. omdev/pyproject/cli.py +1 -1
  14. omdev/pyproject/pkg.py +1 -1
  15. omdev/pyproject/reqs.py +8 -7
  16. omdev/pyproject/tools/aboutdeps.py +5 -0
  17. omdev/scripts/ci.py +361 -25
  18. omdev/scripts/interp.py +43 -8
  19. omdev/scripts/lib/logs.py +117 -21
  20. omdev/scripts/pyproject.py +415 -39
  21. omdev/tools/git/cli.py +43 -13
  22. omdev/tools/json/formats.py +2 -0
  23. omdev/tools/jsonview/cli.py +19 -61
  24. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  25. omdev/tools/pawk/README.md +195 -0
  26. omdev/tui/apps/edit/main.py +5 -1
  27. omdev/tui/apps/irc/app.py +28 -20
  28. omdev/tui/apps/irc/commands.py +1 -1
  29. omdev/tui/rich/__init__.py +12 -0
  30. omdev/tui/textual/__init__.py +41 -2
  31. omdev/tui/textual/app2.py +6 -1
  32. omdev/tui/textual/debug/__init__.py +10 -0
  33. omdev/tui/textual/debug/dominfo.py +151 -0
  34. omdev/tui/textual/debug/screen.py +24 -0
  35. omdev/tui/textual/devtools.py +187 -0
  36. omdev/tui/textual/logging2.py +20 -0
  37. omdev/tui/textual/types.py +45 -0
  38. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/METADATA +10 -6
  39. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/RECORD +43 -34
  40. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/WHEEL +0 -0
  41. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/entry_points.txt +0 -0
  42. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/licenses/LICENSE +0 -0
  43. {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/top_level.txt +0 -0
omdev/tools/git/cli.py CHANGED
@@ -90,7 +90,12 @@ def get_first_commit_of_day(rev: str) -> str | None:
90
90
  class Cli(ap.Cli):
91
91
  @dc.dataclass(frozen=True, kw_only=True)
92
92
  class Config:
93
- default_message_generator: str | None = None
93
+ @dc.dataclass(frozen=True, kw_only=True)
94
+ class MessageGenerator:
95
+ name: str
96
+ config: ta.Mapping[str, ta.Any] | None = None
97
+
98
+ message_generator: str | MessageGenerator | None = None
94
99
 
95
100
  _config_file_path_arg: ta.Optional[str] = ap.arg_('-c', '--config-file-path', nargs='?')
96
101
 
@@ -280,6 +285,35 @@ class Cli(ap.Cli):
280
285
 
281
286
  # Lazy helpers
282
287
 
288
+ def _make_git_message_generator(
289
+ self,
290
+ *,
291
+ name: str | None = None,
292
+ cwd: str | None = None,
293
+ ) -> GitMessageGenerator:
294
+ cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
295
+ kw: dict[str, ta.Any] = {}
296
+
297
+ if name is None:
298
+ if cwd is None:
299
+ cwd = os.getcwd()
300
+
301
+ cfg = self.load_config(cwd).message_generator
302
+ if cfg is None:
303
+ pass
304
+ elif isinstance(cfg, str):
305
+ name = cfg
306
+ elif isinstance(cfg, Cli.Config.MessageGenerator):
307
+ name = cfg.name
308
+ kw.update(cfg.config or {})
309
+ else:
310
+ raise TypeError(cfg)
311
+
312
+ if name is not None:
313
+ cls = load_message_generator_manifests_map()[name].load_cls()
314
+
315
+ return cls(**kw)
316
+
283
317
  @ap.cmd(
284
318
  ap.arg('-g', '--message-generator', nargs='?'),
285
319
  ap.arg('dir', nargs='*'),
@@ -292,12 +326,10 @@ class Cli(ap.Cli):
292
326
  if not (st.has_staged or st.has_dirty):
293
327
  return
294
328
 
295
- mg_cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
296
- if (mg_name := self.args.message_generator) is None:
297
- mg_name = self.load_config(cwd).default_message_generator
298
- if mg_name is not None:
299
- mg_cls = load_message_generator_manifests_map()[mg_name].load_cls()
300
- mg = mg_cls()
329
+ mg = self._make_git_message_generator(
330
+ name=self.args.message_generator,
331
+ cwd=cwd,
332
+ )
301
333
 
302
334
  mgr = mg.generate_commit_message(GitMessageGenerator.GenerateCommitMessageArgs(
303
335
  cwd=cwd,
@@ -338,12 +370,10 @@ class Cli(ap.Cli):
338
370
  msg = self.args.message
339
371
 
340
372
  else:
341
- mg_cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
342
- if (mg_name := self.args.message_generator) is None:
343
- mg_name = self.load_config(cwd).default_message_generator
344
- if mg_name is not None:
345
- mg_cls = load_message_generator_manifests_map()[mg_name].load_cls()
346
- mg = mg_cls()
373
+ mg = self._make_git_message_generator(
374
+ name=self.args.message_generator,
375
+ cwd=cwd,
376
+ )
347
377
 
348
378
  mgr = mg.generate_commit_message(GitMessageGenerator.GenerateCommitMessageArgs(
349
379
  cwd=cwd,
@@ -2,6 +2,8 @@
2
2
  TODO:
3
3
  - options lol - csv header, newline, etc
4
4
  - edn
5
+ - jsonl
6
+ - jsonc (just comments)
5
7
  """
6
8
  import dataclasses as dc
7
9
  import enum
@@ -3,7 +3,7 @@ TODO:
3
3
  - read from stdin
4
4
  - evaluate jmespath on server using extended engine
5
5
  - integrate with json tool
6
- - use omlish server and templates
6
+ - use omlish server
7
7
  - vendor deps, serve local
8
8
  - update to https://github.com/josdejong/svelte-jsoneditor
9
9
  """
@@ -19,61 +19,22 @@ import webbrowser
19
19
 
20
20
  from omlish import check
21
21
  from omlish import lang
22
+ from omlish.sockets.ports import get_available_port
23
+ from omlish.text import minja
22
24
 
23
25
 
24
26
  ##
25
27
 
26
28
 
27
- HTML_TEMPLATE = """
28
- <!DOCTYPE html>
29
- <html lang="en">
30
-
31
- <head>
32
- <meta charset="UTF-8">
33
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
- <title>JSON Viewer</title>
35
- <link
36
- href="https://cdn.jsdelivr.net/npm/jsoneditor/dist/jsoneditor.min.css"
37
- rel="stylesheet"
38
- type="text/css"
39
- >
40
- <style>
41
- {css_src}
42
- </style>
43
- </head>
44
-
45
- <body>
46
- <div class="input-area">
47
- <label for="jmespath-input">JMESPath:</label>
48
- <input
49
- type="text"
50
- id="jmespath-input"
51
- list="jmespath-history"
52
- placeholder="Enter JMESPath expression..."
53
- autocomplete="off"
54
- >
55
- <datalist id="jmespath-history"></datalist>
56
- <span id="error-message"></span>
57
- </div>
58
- <div id="jsoneditor"></div>
59
-
60
- <script src="https://cdn.jsdelivr.net/npm/jsoneditor@10.2.0/dist/jsoneditor.min.js"></script>
61
- <script src="https://cdn.jsdelivr.net/npm/jmespath@0.16.0/jmespath.min.js"></script>
62
- <script>
63
- const originalJsonData = {json_data};
64
- </script>
65
- <script>
66
- {js_src}
67
- </script>
68
- </body>
69
-
70
- </html>
71
- """
29
+ @lang.cached_function
30
+ def html_template() -> minja.MinjaTemplate:
31
+ src = lang.get_relative_resources('resources', globals=globals())['jsonview.html.j2'].read_text()
32
+ return minja.compile_minja_template(src, ['ctx'])
72
33
 
73
34
 
74
35
  def view_json(
75
36
  filepath: str,
76
- port: int,
37
+ port: int | None,
77
38
  *,
78
39
  mode: ta.Literal['jsonl', 'json5', 'json', None] = None,
79
40
  ) -> None:
@@ -120,15 +81,18 @@ def view_json(
120
81
  self.send_response(200)
121
82
  self.send_header('Content-type', 'text/html')
122
83
  self.end_headers()
123
- html_content = HTML_TEMPLATE.format(
124
- css_src=css_src,
125
- js_src=js_src,
84
+ html_content = html_template()(ctx=dict(
85
+ css_src=css_src.strip(),
86
+ js_src=js_src.strip(),
126
87
  json_data=json_string,
127
- )
88
+ ))
128
89
  self.wfile.write(html_content.encode('utf-8'))
129
90
  else:
130
91
  super().do_GET()
131
92
 
93
+ if port is None:
94
+ port = get_available_port()
95
+
132
96
  handler_cls = JsonViewerHttpRequestHandler
133
97
  with socketserver.TCPServer(('127.0.0.1', port), handler_cls) as httpd:
134
98
  url = f'http://127.0.0.1:{port}'
@@ -150,18 +114,12 @@ def _main() -> None:
150
114
  description='Launch a web-based JSON viewer with JMESPath transformations.',
151
115
  formatter_class=argparse.RawTextHelpFormatter,
152
116
  )
153
- parser.add_argument(
154
- 'filepath',
155
- help='The path to the JSON file you want to view.',
156
- )
157
- parser.add_argument(
158
- '-p', '--port',
159
- type=int,
160
- default=(default_port := 8999),
161
- help=f'The port to run the web server on. Defaults to {default_port}.',
162
- )
117
+
118
+ parser.add_argument('filepath')
119
+ parser.add_argument('-p', '--port', type=int)
163
120
  parser.add_argument('-l', '--lines', action='store_true')
164
121
  parser.add_argument('-5', '--five', action='store_true')
122
+
165
123
  args = parser.parse_args()
166
124
 
167
125
  check.state(not (args.lines and args.five))
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>JSON Viewer</title>
8
+ <link
9
+ href="https://cdn.jsdelivr.net/npm/jsoneditor/dist/jsoneditor.min.css"
10
+ rel="stylesheet"
11
+ type="text/css"
12
+ >
13
+ <style>
14
+ {{ ctx['css_src'] }}
15
+ </style>
16
+ </head>
17
+
18
+ <body>
19
+ <div class="input-area">
20
+ <label for="jmespath-input">JMESPath:</label>
21
+ <input
22
+ type="text"
23
+ id="jmespath-input"
24
+ list="jmespath-history"
25
+ placeholder="Enter JMESPath expression..."
26
+ autocomplete="off"
27
+ >
28
+ <datalist id="jmespath-history"></datalist>
29
+ <span id="error-message"></span>
30
+ </div>
31
+ <div id="jsoneditor"></div>
32
+
33
+ <script src="https://cdn.jsdelivr.net/npm/jsoneditor@10.2.0/dist/jsoneditor.min.js"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/jmespath@0.16.0/jmespath.min.js"></script>
35
+ <script>
36
+ const originalJsonData = {{ ctx['json_data'] }};
37
+ </script>
38
+ <script>
39
+ {{ ctx['js_src'] }}
40
+ </script>
41
+ </body>
42
+
43
+ </html>
@@ -0,0 +1,195 @@
1
+ [From alecthomas/pawk](https://github.com/alecthomas/pawk/blob/d60f78399e8a01857ebd73415a00e7eb424043ab/pawk.py)
2
+
3
+ ---
4
+
5
+ PAWK - A Python line processor (like AWK)
6
+
7
+ PAWK aims to bring the full power of Python to AWK-like line-processing.
8
+
9
+ Here are some quick examples to show some of the advantages of pawk over AWK.
10
+
11
+ The first example transforms `/etc/hosts` into a JSON map of host to IP:
12
+
13
+ cat /etc/hosts | pawk -B 'd={}' -E 'json.dumps(d)' '!/^#/ d[f[1]] = f[0]'
14
+
15
+ Breaking this down:
16
+
17
+ 1. `-B 'd={}'` is a begin statement initializing a dictionary, executed once before processing begins.
18
+ 2. `-E 'json.dumps(d)'` is an end statement expression, producing the JSON representation of the dictionary `d`.
19
+ 3. `!/^#/` tells pawk to match any line *not* beginning with `#`.
20
+ 4. `d[f[1]] = f[0]` adds a dictionary entry where the key is the second field in the line (the first hostname) and the
21
+ value is the first field (the IP address).
22
+
23
+ And another example showing how to bzip2-compress + base64-encode a file:
24
+
25
+ cat pawk.py | pawk -E 'base64.encodestring(bz2.compress(t))'
26
+
27
+ ### AWK example translations
28
+
29
+ Most basic AWK constructs are available. You can find more idiomatic examples below in the example section, but here are
30
+ a bunch of awk commands and their equivalent pawk commands to get started with:
31
+
32
+ Print lines matching a pattern:
33
+
34
+ ls -l / | awk '/etc/'
35
+ ls -l / | pawk '/etc/'
36
+
37
+ Print lines *not* matching a pattern:
38
+
39
+ ls -l / | awk '!/etc/'
40
+ ls -l / | pawk '!/etc/'
41
+
42
+ Field slicing and dicing (here pawk wins because of Python's array slicing):
43
+
44
+ ls -l / | awk '/etc/ {print $5, $6, $7, $8, $9}'
45
+ ls -l / | pawk '/etc/ f[4:]'
46
+
47
+ Begin and end actions (in this case, summing the sizes of all files):
48
+
49
+ ls -l | awk 'BEGIN {c = 0} {c += $5} END {print c}'
50
+ ls -l | pawk -B 'c = 0' -E 'c' 'c += int(f[4])'
51
+
52
+ Print files where a field matches a numeric expression (in this case where files are > 1024 bytes):
53
+
54
+ ls -l | awk '$5 > 1024'
55
+ ls -l | pawk 'int(f[4]) > 1024'
56
+
57
+ Matching a single field (any filename with "t" in it):
58
+
59
+ ls -l | awk '$NF ~/t/'
60
+ ls -l | pawk '"t" in f[-1]'
61
+
62
+ ## Expression evaluation
63
+
64
+ PAWK evaluates a Python expression or statement against each line in stdin. The following variables are available in
65
+ local context:
66
+
67
+ - `line` - Current line text, including newline.
68
+ - `l` - Current line text, excluding newline.
69
+ - `n` - The current 1-based line number.
70
+ - `f` - Fields of the line (split by the field separator `-F`).
71
+ - `nf` - Number of fields in this line.
72
+ - `m` - Tuple of match regular expression capture groups, if any.
73
+
74
+
75
+ In the context of the `-E` block:
76
+
77
+ - `t` - The entire input text up to the current cursor position.
78
+
79
+ If the flag `-H, --header` is provided, each field in the first row of the input will be treated as field variable names
80
+ in subsequent rows. The header is not output. For example, given the input:
81
+
82
+ ```
83
+ count name
84
+ 12 bob
85
+ 34 fred
86
+ ```
87
+
88
+ We could do:
89
+
90
+ ```
91
+ $ pawk -H '"%s is %s" % (name, count)' < input.txt
92
+ bob is 12
93
+ fred is 34
94
+ ```
95
+
96
+ To output a header as well, use `-B`:
97
+
98
+ ```
99
+ $ pawk -H -B '"name is count"' '"%s is %s" % (name, count)' < input.txt
100
+ name is count
101
+ bob is 12
102
+ fred is 34
103
+ ```
104
+
105
+ Module references will be automatically imported if possible. Additionally, the `--import <module>[,<module>,...]` flag
106
+ can be used to import symbols from a set of modules into the evaluation context.
107
+
108
+ eg. `--import os.path` will import all symbols from `os.path`, such as `os.path.isfile()`, into the context.
109
+
110
+ ## Output
111
+
112
+ ### Line actions
113
+
114
+ The type of the evaluated expression determines how output is displayed:
115
+
116
+ - `tuple` or `list`: the elements are converted to strings and joined with the output delimiter (`-O`).
117
+ - `None` or `False`: nothing is output for that line.
118
+ - `True`: the original line is output.
119
+ - Any other value is converted to a string.
120
+
121
+ ### Start/end blocks
122
+
123
+ The rules are the same as for line actions with one difference. Because there is no "line" that corresponds to them, an
124
+ expression returning True is ignored.
125
+
126
+ $ echo -ne 'foo\nbar' | pawk -E t
127
+ foo
128
+ bar
129
+
130
+
131
+ ## Command-line usage
132
+
133
+ ```
134
+ Usage: cat input | pawk [<options>] <expr>
135
+
136
+ A Python line-processor (like awk).
137
+
138
+ See https://github.com/alecthomas/pawk for details. Based on
139
+ http://code.activestate.com/recipes/437932/.
140
+
141
+ Options:
142
+ -h, --help show this help message and exit
143
+ -I <filename>, --in_place=<filename>
144
+ modify given input file in-place
145
+ -i <modules>, --import=<modules>
146
+ comma-separated list of modules to "from x import *"
147
+ from
148
+ -F <delim> input delimiter
149
+ -O <delim> output delimiter
150
+ -L <delim> output line separator
151
+ -B <statement>, --begin=<statement>
152
+ begin statement
153
+ -E <statement>, --end=<statement>
154
+ end statement
155
+ -s, --statement DEPRECATED. retained for backward compatibility
156
+ -H, --header use first row as field variable names in subsequent
157
+ rows
158
+ --strict abort on exceptions
159
+ ```
160
+
161
+ ## Examples
162
+
163
+ ### Line processing
164
+
165
+ Print the name and size of every file from stdin:
166
+
167
+ find . -type f | pawk 'f[0], os.stat(f[0]).st_size'
168
+
169
+ > **Note:** this example also shows how pawk automatically imports referenced modules, in this case `os`.
170
+
171
+ Print the sum size of all files from stdin:
172
+
173
+ find . -type f | \
174
+ pawk \
175
+ --begin 'c=0' \
176
+ --end c \
177
+ 'c += os.stat(f[0]).st_size'
178
+
179
+ Short-flag version:
180
+
181
+ find . -type f | pawk -B c=0 -E c 'c += os.stat(f[0]).st_size'
182
+
183
+
184
+ ### Whole-file processing
185
+
186
+ If you do not provide a line expression, but do provide an end statement, pawk will accumulate each line, and the entire
187
+ file's text will be available in the end statement as `t`. This is useful for operations on entire files, like the
188
+ following example of converting a file from markdown to HTML:
189
+
190
+ cat README.md | \
191
+ pawk --end 'markdown.markdown(t)'
192
+
193
+ Short-flag version:
194
+
195
+ cat README.md | pawk -E 'markdown.markdown(t)'
@@ -19,13 +19,17 @@ class QuitConfirmScreen(tx.ModalScreen[bool]):
19
19
  #dialog {
20
20
  width: 60;
21
21
  height: 11;
22
+
22
23
  border: thick $background 80%;
23
- background: $surface;
24
+
24
25
  padding: 1 2;
26
+
27
+ background: $surface;
25
28
  }
26
29
 
27
30
  #question {
28
31
  height: 3;
32
+
29
33
  content-align: center middle;
30
34
  }
31
35
 
omdev/tui/apps/irc/app.py CHANGED
@@ -43,26 +43,34 @@ class IrcApp(tx.App):
43
43
  _commands: ta.ClassVar[ta.Mapping[str, IrcCommand]] = ALL_COMMANDS
44
44
 
45
45
  CSS = """
46
- #messages {
47
- height: 1fr;
48
- overflow-y: auto;
49
- border: none;
50
- padding: 0;
51
- }
52
-
53
- #status {
54
- height: 1;
55
- background: $primary;
56
- color: $text;
57
- border: none;
58
- padding: 0;
59
- }
60
-
61
- #input {
62
- dock: bottom;
63
- border: none;
64
- padding: 0;
65
- }
46
+ #messages {
47
+ height: 1fr;
48
+
49
+ border: none;
50
+
51
+ padding: 0;
52
+
53
+ overflow-y: auto;
54
+ }
55
+
56
+ #status {
57
+ height: 1;
58
+
59
+ border: none;
60
+
61
+ padding: 0;
62
+
63
+ color: $text;
64
+ background: $primary;
65
+ }
66
+
67
+ #input {
68
+ border: none;
69
+
70
+ padding: 0;
71
+
72
+ dock: bottom;
73
+ }
66
74
  """
67
75
 
68
76
  BINDINGS: ta.ClassVar[ta.Sequence[tx.Binding]] = [
@@ -13,7 +13,7 @@ if ta.TYPE_CHECKING:
13
13
  ##
14
14
 
15
15
 
16
- class IrcCommand(abc.ABC):
16
+ class IrcCommand(lang.Abstract):
17
17
  """Abstract base class for IRC commands."""
18
18
 
19
19
  def __init__(self) -> None:
@@ -6,15 +6,27 @@ from omlish import lang as _lang
6
6
  with _lang.auto_proxy_init(globals()):
7
7
  ##
8
8
 
9
+ from rich import align # noqa
9
10
  from rich import console # noqa
10
11
  from rich import live # noqa
11
12
  from rich import markdown # noqa
12
13
  from rich import repr # noqa
13
14
  from rich import text # noqa
15
+ from rich.align import Align # noqa
16
+ from rich.color import Color # noqa
17
+ from rich.color import blend_rgb # noqa
18
+ from rich.color_triplet import ColorTriplet # noqa
14
19
  from rich.console import Console # noqa
20
+ from rich.console import Group # noqa
15
21
  from rich.live import Live # noqa
16
22
  from rich.markdown import Markdown # noqa
23
+ from rich.segment import Segment # noqa
24
+ from rich.segment import SegmentLines # noqa
25
+ from rich.style import Style # noqa
26
+ from rich.syntax import Syntax # noqa
27
+ from rich.table import Table # noqa
17
28
  from rich.text import Text # noqa
29
+ from rich.theme import Theme # noqa
18
30
 
19
31
  ##
20
32
 
@@ -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.option_list import OptionDoesNotExist # noqa
214
- from textual.widgets.option_list import Option # noqa
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(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,10 @@
1
+ # ruff: noqa: F401
2
+ # flake8: noqa: F401
3
+ from omlish import lang as _lang
4
+
5
+
6
+ with _lang.auto_proxy_init(globals()):
7
+ ##
8
+
9
+ from . import dominfo # noqa
10
+ from . import screen # noqa