omdev 0.0.0.dev439__py3-none-any.whl → 0.0.0.dev486__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/__about__.py +9 -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 +1 -2
- omdev/ci/github/api/v2/api.py +2 -0
- omdev/cmdlog/cli.py +1 -2
- omdev/dataclasses/_dumping.py +1960 -0
- omdev/dataclasses/_template.py +22 -0
- omdev/dataclasses/cli.py +6 -1
- omdev/dataclasses/codegen.py +340 -60
- omdev/dataclasses/dumping.py +200 -0
- omdev/interp/uv/provider.py +1 -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/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/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 +6 -7
- 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 +629 -0
- omdev/pyproject/pkg.py +190 -45
- omdev/pyproject/reqs.py +31 -9
- omdev/pyproject/tools/__init__.py +0 -0
- omdev/pyproject/tools/aboutdeps.py +55 -0
- omdev/pyproject/venvs.py +8 -1
- omdev/rs/__init__.py +0 -0
- omdev/scripts/ci.py +400 -80
- omdev/scripts/interp.py +193 -35
- omdev/scripts/lib/__init__.py +0 -0
- omdev/scripts/{inject.py → lib/inject.py} +75 -28
- omdev/scripts/lib/logs.py +2079 -0
- omdev/scripts/{marshal.py → lib/marshal.py} +68 -26
- omdev/scripts/pyproject.py +941 -90
- omdev/tools/git/cli.py +12 -1
- omdev/tools/json/processing.py +5 -2
- omdev/tools/jsonview/cli.py +31 -5
- 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 +163 -0
- omdev/tui/apps/irc/__init__.py +0 -0
- omdev/tui/apps/irc/__main__.py +4 -0
- omdev/tui/apps/irc/app.py +278 -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 +34 -0
- omdev/tui/rich/console2.py +20 -0
- omdev/tui/rich/markdown2.py +186 -0
- omdev/tui/textual/__init__.py +226 -0
- omdev/tui/textual/app2.py +11 -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/drivers2.py +55 -0
- {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/METADATA +11 -9
- {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/RECORD +121 -73
- 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.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev439.dist-info → omdev-0.0.0.dev486.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from textual.cache import LRUCache
|
|
6
|
+
from textual.content import Content
|
|
7
|
+
from textual.widgets import Input
|
|
8
|
+
|
|
9
|
+
from .widget import AutoComplete
|
|
10
|
+
from .widget import AutoCompleteItem
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PathAutoCompleteItem(AutoCompleteItem):
|
|
17
|
+
def __init__(self, completion: str, path: pathlib.Path) -> None:
|
|
18
|
+
super().__init__(completion)
|
|
19
|
+
|
|
20
|
+
self.path = path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_path_input_sort_key(item: PathAutoCompleteItem) -> tuple[bool, bool, str]:
|
|
24
|
+
"""
|
|
25
|
+
Sort key function for results within the dropdown.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
item: The PathAutoCompleteItem to get a sort key for.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A tuple of (is_dotfile, is_file, lowercase_name) for sorting.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
name = item.path.name
|
|
35
|
+
is_dotfile = name.startswith('.')
|
|
36
|
+
return (not item.path.is_dir(), not is_dotfile, name.lower())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PathAutoComplete(AutoComplete):
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
target: Input | str,
|
|
43
|
+
path: str | pathlib.Path = '.',
|
|
44
|
+
*,
|
|
45
|
+
show_dotfiles: bool = True,
|
|
46
|
+
sort_key: ta.Callable[[PathAutoCompleteItem], ta.Any] = default_path_input_sort_key,
|
|
47
|
+
folder_prefix: Content = Content('📂'),
|
|
48
|
+
file_prefix: Content = Content('📄'),
|
|
49
|
+
prevent_default_enter: bool = True,
|
|
50
|
+
prevent_default_tab: bool = True,
|
|
51
|
+
cache_size: int = 100,
|
|
52
|
+
name: str | None = None,
|
|
53
|
+
id: str | None = None, # noqa
|
|
54
|
+
classes: str | None = None,
|
|
55
|
+
disabled: bool = False,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
An autocomplete widget for filesystem paths.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
target: The target input widget to autocomplete.
|
|
62
|
+
path: The base path to autocomplete from.
|
|
63
|
+
show_dotfiles: Whether to show dotfiles (files/dirs starting with ".").
|
|
64
|
+
sort_key: Function to sort the dropdown items.
|
|
65
|
+
folder_prefix: The prefix for folder items (e.g. 📂).
|
|
66
|
+
file_prefix: The prefix for file items (e.g. 📄).
|
|
67
|
+
prevent_default_enter: Whether to prevent the default enter behavior.
|
|
68
|
+
prevent_default_tab: Whether to prevent the default tab behavior.
|
|
69
|
+
cache_size: The number of directories to cache.
|
|
70
|
+
name: The name of the widget.
|
|
71
|
+
id: The DOM node id of the widget.
|
|
72
|
+
classes: The CSS classes of the widget.
|
|
73
|
+
disabled: Whether the widget is disabled.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
super().__init__(
|
|
77
|
+
target,
|
|
78
|
+
None,
|
|
79
|
+
prevent_default_enter=prevent_default_enter,
|
|
80
|
+
prevent_default_tab=prevent_default_tab,
|
|
81
|
+
name=name,
|
|
82
|
+
id=id,
|
|
83
|
+
classes=classes,
|
|
84
|
+
disabled=disabled,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self.path = pathlib.Path(path) if isinstance(path, str) else path
|
|
88
|
+
self.show_dotfiles = show_dotfiles
|
|
89
|
+
self.sort_key = sort_key
|
|
90
|
+
self.folder_prefix = folder_prefix
|
|
91
|
+
self.file_prefix = file_prefix
|
|
92
|
+
self._directory_cache: LRUCache[str, list[os.DirEntry[str]]] = LRUCache(cache_size)
|
|
93
|
+
|
|
94
|
+
def get_candidates(self, target_state: AutoComplete.TargetState) -> list[AutoCompleteItem]:
|
|
95
|
+
"""
|
|
96
|
+
Get the candidates for the current path segment.
|
|
97
|
+
|
|
98
|
+
This is called each time the input changes or the cursor position changes/
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
current_input = target_state.text[: target_state.cursor_position]
|
|
102
|
+
|
|
103
|
+
if '/' in current_input:
|
|
104
|
+
last_slash_index = current_input.rindex('/')
|
|
105
|
+
path_segment = current_input[:last_slash_index] or '/'
|
|
106
|
+
directory = self.path / path_segment if path_segment != '/' else self.path
|
|
107
|
+
else:
|
|
108
|
+
directory = self.path
|
|
109
|
+
|
|
110
|
+
# Use the directory path as the cache key
|
|
111
|
+
cache_key = str(directory)
|
|
112
|
+
cached_entries = self._directory_cache.get(cache_key)
|
|
113
|
+
|
|
114
|
+
if cached_entries is not None:
|
|
115
|
+
entries = cached_entries
|
|
116
|
+
else:
|
|
117
|
+
try:
|
|
118
|
+
entries = list(os.scandir(directory))
|
|
119
|
+
self._directory_cache[cache_key] = entries
|
|
120
|
+
except OSError:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
results: list[PathAutoCompleteItem] = []
|
|
124
|
+
for entry in entries:
|
|
125
|
+
# Only include the entry name, not the full path
|
|
126
|
+
completion = entry.name
|
|
127
|
+
if not self.show_dotfiles and completion.startswith('.'):
|
|
128
|
+
continue
|
|
129
|
+
if entry.is_dir():
|
|
130
|
+
completion += '/'
|
|
131
|
+
results.append(PathAutoCompleteItem(completion, path=pathlib.Path(entry.path)))
|
|
132
|
+
|
|
133
|
+
results.sort(key=self.sort_key)
|
|
134
|
+
folder_prefix = self.folder_prefix
|
|
135
|
+
file_prefix = self.file_prefix
|
|
136
|
+
return [
|
|
137
|
+
AutoCompleteItem(
|
|
138
|
+
item.main,
|
|
139
|
+
prefix=folder_prefix if item.path.is_dir() else file_prefix,
|
|
140
|
+
)
|
|
141
|
+
for item in results
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
def get_search_string(self, target_state: AutoComplete.TargetState) -> str:
|
|
145
|
+
"""Return only the current path segment for searching in the dropdown."""
|
|
146
|
+
|
|
147
|
+
current_input = target_state.text[: target_state.cursor_position]
|
|
148
|
+
|
|
149
|
+
if '/' in current_input:
|
|
150
|
+
last_slash_index = current_input.rindex('/')
|
|
151
|
+
search_string = current_input[last_slash_index + 1:]
|
|
152
|
+
return search_string
|
|
153
|
+
else:
|
|
154
|
+
return current_input
|
|
155
|
+
|
|
156
|
+
def apply_completion(self, value: str, state: AutoComplete.TargetState) -> None:
|
|
157
|
+
"""Apply the completion by replacing only the current path segment."""
|
|
158
|
+
|
|
159
|
+
target = self.target
|
|
160
|
+
current_input = state.text
|
|
161
|
+
cursor_position = state.cursor_position
|
|
162
|
+
|
|
163
|
+
# There's a slash before the cursor, so we only want to replace the text after the last slash with the selected
|
|
164
|
+
# value
|
|
165
|
+
try:
|
|
166
|
+
replace_start_index = current_input.rindex('/', 0, cursor_position)
|
|
167
|
+
except ValueError:
|
|
168
|
+
# No slashes, so we do a full replacement
|
|
169
|
+
new_value = value
|
|
170
|
+
new_cursor_position = len(value)
|
|
171
|
+
else:
|
|
172
|
+
# Keep everything before and including the slash before the cursor.
|
|
173
|
+
path_prefix = current_input[: replace_start_index + 1]
|
|
174
|
+
new_value = path_prefix + value
|
|
175
|
+
new_cursor_position = len(path_prefix) + len(value)
|
|
176
|
+
|
|
177
|
+
with self.prevent(Input.Changed):
|
|
178
|
+
target.value = new_value
|
|
179
|
+
target.cursor_position = new_cursor_position
|
|
180
|
+
|
|
181
|
+
def post_completion(self) -> None:
|
|
182
|
+
if not self.target.value.endswith('/'):
|
|
183
|
+
self.action_hide()
|
|
184
|
+
|
|
185
|
+
def should_show_dropdown(self, search_string: str) -> bool:
|
|
186
|
+
return (
|
|
187
|
+
super().should_show_dropdown(search_string) or
|
|
188
|
+
(
|
|
189
|
+
(search_string == '' and self.target.value != '') and
|
|
190
|
+
self.option_list.option_count > 1
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def clear_directory_cache(self) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Clear the directory cache. If you know that the contents of the directory have changed, you can call this method
|
|
197
|
+
to invalidate the cache.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
self._directory_cache.clear()
|
|
201
|
+
target_state = self._get_target_state()
|
|
202
|
+
self._rebuild_options(target_state, self.get_search_string(target_state))
|