machineconfig 7.64__py3-none-any.whl → 7.83__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 machineconfig might be problematic. Click here for more details.
- machineconfig/cluster/sessions_managers/utils/maker.py +4 -2
- machineconfig/jobs/installer/custom/yazi.py +120 -0
- machineconfig/jobs/installer/custom_dev/nerdfont.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +26 -12
- machineconfig/jobs/installer/custom_dev/sysabc.py +26 -5
- machineconfig/jobs/installer/installer_data.json +232 -96
- machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
- machineconfig/profile/create_helper.py +0 -12
- machineconfig/profile/create_links_export.py +2 -2
- machineconfig/profile/mapper.toml +2 -2
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/python/agents.py +22 -17
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +4 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +1 -1
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +13 -13
- machineconfig/scripts/python/ftpx.py +36 -12
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
- machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
- machineconfig/scripts/python/helpers_cloud/cloud_mount.py +19 -17
- machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/start_slidev.py +6 -7
- machineconfig/scripts/python/helpers_devops/cli_config.py +10 -0
- machineconfig/scripts/python/helpers_devops/cli_nw.py +90 -10
- machineconfig/scripts/python/helpers_devops/cli_self.py +8 -7
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +7 -7
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +12 -11
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +8 -10
- machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -1
- machineconfig/scripts/python/helpers_devops/devops_status.py +7 -19
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +20 -9
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +2 -2
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +58 -1
- machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +5 -3
- machineconfig/scripts/python/helpers_repos/count_lines.py +40 -11
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_utils/download.py +4 -3
- machineconfig/scripts/python/helpers_utils/path.py +87 -34
- machineconfig/scripts/python/interactive.py +1 -1
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +55 -6
- machineconfig/scripts/python/nw/address.py +132 -0
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +8 -5
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -11
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/nushell/config.nu +2 -2
- machineconfig/settings/shells/nushell/env.nu +45 -6
- machineconfig/settings/shells/nushell/init.nu +282 -95
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/yazi/init.lua +4 -0
- machineconfig/settings/yazi/keymap_linux.toml +11 -4
- machineconfig/settings/yazi/theme.toml +4 -0
- machineconfig/settings/yazi/yazi_linux.toml +84 -0
- machineconfig/settings/yazi/yazi_windows.toml +58 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +4 -2
- machineconfig/utils/installer_utils/github_release_bulk.py +104 -62
- machineconfig/utils/installer_utils/install_from_url.py +200 -0
- machineconfig/utils/installer_utils/installer_class.py +25 -74
- machineconfig/utils/installer_utils/installer_cli.py +40 -50
- machineconfig/utils/installer_utils/installer_helper.py +100 -0
- machineconfig/utils/installer_utils/installer_runner.py +5 -8
- machineconfig/utils/links.py +2 -2
- machineconfig/utils/meta.py +2 -2
- machineconfig/utils/options.py +3 -3
- machineconfig/utils/path_extended.py +1 -1
- machineconfig/utils/path_helper.py +0 -1
- machineconfig/utils/ssh.py +143 -409
- machineconfig/utils/ssh_utils/abc.py +8 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +110 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
- machineconfig/utils/ssh_utils/utils.py +141 -0
- machineconfig/utils/ssh_utils/wsl.py +210 -0
- machineconfig/utils/upgrade_packages.py +2 -1
- machineconfig/utils/ve.py +11 -4
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/METADATA +2 -2
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/RECORD +96 -89
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/entry_points.txt +2 -2
- machineconfig/scripts/python/explore.py +0 -49
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfag +0 -17
- machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfrga +0 -21
- machineconfig/scripts/python/helpers_msearch/scripts_linux/skrg +0 -4
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfb.ps1 +0 -3
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfrga.bat +0 -20
- machineconfig/settings/yazi/yazi.toml +0 -17
- machineconfig/setup_linux/others/cli_installation.sh +0 -137
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_fire_command/f.py} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/WHEEL +0 -0
- {machineconfig-7.64.dist-info → machineconfig-7.83.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.13"
|
|
4
|
+
# dependencies = [
|
|
5
|
+
# "machineconfig>=7.83",
|
|
6
|
+
# "textual",
|
|
7
|
+
# "pyperclip",
|
|
8
|
+
# ]
|
|
9
|
+
# ///
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from typing import Final
|
|
17
|
+
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
from textual import on
|
|
20
|
+
from textual.app import App, ComposeResult
|
|
21
|
+
from textual.binding import Binding
|
|
22
|
+
from textual.containers import Horizontal, Vertical
|
|
23
|
+
from textual.widgets import Footer, Header, Label, ListItem, ListView, Static
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
VALUE_PREVIEW_LIMIT: Final[int] = 4096
|
|
27
|
+
SUMMARY_LIMIT: Final[int] = 96
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def truncate_text(text: str, limit: int) -> tuple[str, int]:
|
|
31
|
+
length = len(text)
|
|
32
|
+
if length <= limit:
|
|
33
|
+
return text, 0
|
|
34
|
+
return text[:limit], length - limit
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def format_summary(env_key: str, env_value: str, limit: int) -> str:
|
|
38
|
+
sanitized = env_value.replace("\n", "\\n").replace("\t", "\\t")
|
|
39
|
+
preview, remainder = truncate_text(sanitized, limit)
|
|
40
|
+
if preview == "":
|
|
41
|
+
base = f"{env_key} = <empty>"
|
|
42
|
+
else:
|
|
43
|
+
base = f"{env_key} = {preview}"
|
|
44
|
+
if remainder == 0:
|
|
45
|
+
return base
|
|
46
|
+
return f"{base}... (+{remainder} chars)"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def collect_environment(env: Mapping[str, str]) -> list[tuple[str, str]]:
|
|
50
|
+
return sorted(env.items(), key=lambda pair: pair[0].lower())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class EnvListItem(ListItem):
|
|
54
|
+
def __init__(self, env_key: str, summary: str) -> None:
|
|
55
|
+
super().__init__(Label(summary))
|
|
56
|
+
self._env_key = env_key
|
|
57
|
+
|
|
58
|
+
def env_key(self) -> str:
|
|
59
|
+
return self._env_key
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class EnvValuePreview(Static):
|
|
63
|
+
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
65
|
+
self.border_title = "Environment Value"
|
|
66
|
+
|
|
67
|
+
def show_value(self, env_key: str, env_value: str) -> None:
|
|
68
|
+
preview, remainder = truncate_text(env_value, VALUE_PREVIEW_LIMIT)
|
|
69
|
+
text = Text()
|
|
70
|
+
text.append(f"{env_key}\n\n", style="bold cyan")
|
|
71
|
+
if preview == "":
|
|
72
|
+
text.append("<empty>", style="dim")
|
|
73
|
+
else:
|
|
74
|
+
text.append(preview)
|
|
75
|
+
if remainder > 0:
|
|
76
|
+
text.append(f"\n... truncated {remainder} characters", style="yellow")
|
|
77
|
+
self.update(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class StatusBar(Static):
|
|
81
|
+
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
|
|
82
|
+
super().__init__(*args, **kwargs)
|
|
83
|
+
self.border_title = "Status"
|
|
84
|
+
|
|
85
|
+
def show_message(self, message: str, level: str) -> None:
|
|
86
|
+
palette = {
|
|
87
|
+
"info": "cyan",
|
|
88
|
+
"success": "green",
|
|
89
|
+
"warning": "yellow",
|
|
90
|
+
"error": "red",
|
|
91
|
+
}
|
|
92
|
+
color = palette.get(level, "white")
|
|
93
|
+
self.update(f"[{color}]{message}[/{color}]")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class EnvExplorerApp(App[None]):
|
|
97
|
+
CSS = """
|
|
98
|
+
Screen { background: $surface; }
|
|
99
|
+
Header { background: $primary; color: $text; }
|
|
100
|
+
Footer { background: $panel; }
|
|
101
|
+
#main-container { height: 100%; }
|
|
102
|
+
#left-panel { width: 50%; height: 100%; border: solid $primary; padding: 1; }
|
|
103
|
+
#right-panel { width: 50%; height: 100%; border: solid $accent; padding: 1; }
|
|
104
|
+
ListView { height: 1fr; border: solid $accent; background: $surface; }
|
|
105
|
+
ListView > ListItem { padding: 0 1; }
|
|
106
|
+
EnvValuePreview { height: 1fr; border: solid $primary; background: $surface; padding: 1; overflow-y: auto; }
|
|
107
|
+
StatusBar { height: 3; border: solid $success; background: $surface; padding: 1; }
|
|
108
|
+
Label { padding: 0 1; height: auto; }
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
BINDINGS = [
|
|
112
|
+
Binding("q", "quit", "Quit", show=True),
|
|
113
|
+
Binding("r", "refresh", "Refresh", show=True),
|
|
114
|
+
Binding("c", "copy_entry", "Copy", show=True),
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
def __init__(self) -> None:
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._env_pairs: list[tuple[str, str]] = []
|
|
120
|
+
self._env_lookup: dict[str, str] = {}
|
|
121
|
+
self._selected_key: str = ""
|
|
122
|
+
|
|
123
|
+
def compose(self) -> ComposeResult:
|
|
124
|
+
platform_name = platform.system()
|
|
125
|
+
yield Header(show_clock=True)
|
|
126
|
+
with Horizontal(id="main-container"):
|
|
127
|
+
with Vertical(id="left-panel"):
|
|
128
|
+
yield Label(f"🌐 Environment Variables ({platform_name})")
|
|
129
|
+
yield ListView(id="env-list")
|
|
130
|
+
with Vertical(id="right-panel"):
|
|
131
|
+
yield EnvValuePreview(id="preview")
|
|
132
|
+
yield StatusBar(id="status")
|
|
133
|
+
yield Footer()
|
|
134
|
+
|
|
135
|
+
def on_mount(self) -> None:
|
|
136
|
+
self.title = "Environment Explorer"
|
|
137
|
+
self.sub_title = f"Platform: {platform.system()}"
|
|
138
|
+
self._reload_environment()
|
|
139
|
+
self._status().show_message("Ready. Select a variable to preview its value.", "info")
|
|
140
|
+
|
|
141
|
+
def _reload_environment(self) -> None:
|
|
142
|
+
self._env_pairs = collect_environment(os.environ)
|
|
143
|
+
self._env_lookup = dict(self._env_pairs)
|
|
144
|
+
self._populate_list()
|
|
145
|
+
|
|
146
|
+
def _populate_list(self) -> None:
|
|
147
|
+
list_view = self.query_one("#env-list", ListView)
|
|
148
|
+
list_view.clear()
|
|
149
|
+
for env_key, env_value in self._env_pairs:
|
|
150
|
+
summary = format_summary(env_key, env_value, SUMMARY_LIMIT)
|
|
151
|
+
list_view.append(EnvListItem(env_key, summary))
|
|
152
|
+
self._status().show_message(f"Loaded {len(self._env_pairs)} environment variables.", "success")
|
|
153
|
+
|
|
154
|
+
def _status(self) -> StatusBar:
|
|
155
|
+
return self.query_one("#status", StatusBar)
|
|
156
|
+
|
|
157
|
+
def _preview(self) -> EnvValuePreview:
|
|
158
|
+
return self.query_one("#preview", EnvValuePreview)
|
|
159
|
+
|
|
160
|
+
@on(ListView.Highlighted)
|
|
161
|
+
def handle_highlight(self, event: ListView.Highlighted) -> None:
|
|
162
|
+
if not isinstance(event.item, EnvListItem):
|
|
163
|
+
return
|
|
164
|
+
env_key = event.item.env_key()
|
|
165
|
+
env_value = self._env_lookup.get(env_key, "")
|
|
166
|
+
self._preview().show_value(env_key, env_value)
|
|
167
|
+
self._status().show_message(f"Previewing {env_key}", "info")
|
|
168
|
+
|
|
169
|
+
@on(ListView.Selected)
|
|
170
|
+
def handle_selection(self, event: ListView.Selected) -> None:
|
|
171
|
+
if not isinstance(event.item, EnvListItem):
|
|
172
|
+
return
|
|
173
|
+
env_key = event.item.env_key()
|
|
174
|
+
self._selected_key = env_key
|
|
175
|
+
env_value = self._env_lookup.get(env_key, "")
|
|
176
|
+
self._preview().show_value(env_key, env_value)
|
|
177
|
+
self._status().show_message(f"Selected {env_key}", "success")
|
|
178
|
+
|
|
179
|
+
def action_refresh(self) -> None:
|
|
180
|
+
self._reload_environment()
|
|
181
|
+
self._status().show_message("Environment reloaded.", "success")
|
|
182
|
+
|
|
183
|
+
def action_copy_entry(self) -> None:
|
|
184
|
+
if self._selected_key == "":
|
|
185
|
+
self._status().show_message("No variable selected.", "warning")
|
|
186
|
+
return
|
|
187
|
+
env_value = self._env_lookup.get(self._selected_key, "")
|
|
188
|
+
payload = f"{self._selected_key}={env_value}"
|
|
189
|
+
try:
|
|
190
|
+
import pyperclip # type: ignore[import]
|
|
191
|
+
|
|
192
|
+
pyperclip.copy(payload)
|
|
193
|
+
self._status().show_message(f"Copied {self._selected_key} to clipboard.", "success")
|
|
194
|
+
except ImportError:
|
|
195
|
+
self._status().show_message("pyperclip unavailable. Install it for clipboard support.", "warning")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def main() -> None:
|
|
199
|
+
app = EnvExplorerApp()
|
|
200
|
+
app.run()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
main()
|
|
@@ -7,20 +7,16 @@ fire
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from machineconfig.utils.ve import get_ve_path_and_ipython_profile
|
|
11
|
-
from machineconfig.utils.accessories import get_repo_root, randstr
|
|
12
|
-
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import FireJobArgs, extract_kwargs, parse_fire_args_from_context
|
|
13
|
-
from machineconfig.utils.path_helper import get_choice_file
|
|
14
|
-
|
|
15
|
-
import platform
|
|
16
10
|
from typing import Optional, Annotated
|
|
17
|
-
from pathlib import Path
|
|
18
11
|
import typer
|
|
19
12
|
|
|
20
13
|
|
|
21
|
-
def route(args: FireJobArgs, fire_args: str = "") -> None:
|
|
14
|
+
def route(args: "FireJobArgs", fire_args: str = "") -> None:
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from machineconfig.utils.path_helper import get_choice_file
|
|
17
|
+
from machineconfig.utils.accessories import get_repo_root, randstr
|
|
22
18
|
choice_file = get_choice_file(args.path, suffixes=None)
|
|
23
|
-
repo_root = get_repo_root(
|
|
19
|
+
repo_root = get_repo_root(choice_file)
|
|
24
20
|
print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
|
|
25
21
|
if args.marimo:
|
|
26
22
|
print(f"🧽 Preparing to launch Marimo notebook for `{choice_file}`...")
|
|
@@ -38,6 +34,7 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
38
34
|
|
|
39
35
|
# ========================= preparing kwargs_dict
|
|
40
36
|
if choice_file.suffix == ".py":
|
|
37
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import extract_kwargs
|
|
41
38
|
kwargs_dict = extract_kwargs(args) # This now returns empty dict, but kept for compatibility
|
|
42
39
|
else:
|
|
43
40
|
kwargs_dict = {}
|
|
@@ -52,17 +49,17 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
52
49
|
choice_function = args.function
|
|
53
50
|
|
|
54
51
|
if choice_file.suffix == ".py":
|
|
55
|
-
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import get_command_streamlit
|
|
56
|
-
|
|
57
52
|
with_project = f"--project {repo_root} " if repo_root is not None else ""
|
|
58
53
|
if args.streamlit:
|
|
54
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import get_command_streamlit
|
|
59
55
|
exe = get_command_streamlit(choice_file=choice_file, environment=args.environment, repo_root=repo_root)
|
|
60
56
|
exe = f"uv run {with_project} {exe} "
|
|
61
57
|
elif args.jupyter:
|
|
62
58
|
exe = f"uv run {with_project} jupyter-lab"
|
|
63
59
|
else:
|
|
64
60
|
if args.interactive:
|
|
65
|
-
|
|
61
|
+
from machineconfig.utils.ve import get_ve_path_and_ipython_profile
|
|
62
|
+
_ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(init_path=choice_file)
|
|
66
63
|
if ipy_profile is None:
|
|
67
64
|
ipy_profile = "default"
|
|
68
65
|
exe = f"uv run {with_project} ipython -i --no-banner --profile {ipy_profile} "
|
|
@@ -106,6 +103,7 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
106
103
|
|
|
107
104
|
# ========================= determining basic command structure: putting together exe & choice_file & choice_function & pdb
|
|
108
105
|
if args.debug:
|
|
106
|
+
import platform
|
|
109
107
|
if platform.system() == "Windows":
|
|
110
108
|
command = f"{exe} -m ipdb {choice_file} " # pudb is not available on windows machines, use poor man's debugger instead.
|
|
111
109
|
elif platform.system() in ["Linux", "Darwin"]:
|
|
@@ -173,6 +171,7 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
173
171
|
export_line = add_to_path(path_variable="PYTHONPATH", directory=str(repo_root))
|
|
174
172
|
command = export_line + "\n" + command
|
|
175
173
|
if args.loop:
|
|
174
|
+
import platform
|
|
176
175
|
if platform.system() in ["Linux", "Darwin"]:
|
|
177
176
|
command = command + "\nsleep 0.5"
|
|
178
177
|
elif platform.system() == "Windows":
|
|
@@ -214,6 +213,7 @@ def fire(
|
|
|
214
213
|
"""Main function to process fire jobs arguments."""
|
|
215
214
|
|
|
216
215
|
# Get Fire arguments from context
|
|
216
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import FireJobArgs, parse_fire_args_from_context
|
|
217
217
|
fire_args = parse_fire_args_from_context(ctx)
|
|
218
218
|
|
|
219
219
|
args = FireJobArgs(
|
|
@@ -266,4 +266,4 @@ def main():
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
if __name__ == "__main__":
|
|
269
|
-
|
|
269
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_args_helper import FireJobArgs
|
|
@@ -6,17 +6,7 @@ Currently, the only way to work around this is to predifine the host in ~/.ssh/c
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
|
-
from
|
|
10
|
-
from rich.console import Console
|
|
11
|
-
from rich.panel import Panel
|
|
12
|
-
|
|
13
|
-
from machineconfig.utils.ssh import SSH
|
|
14
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
15
|
-
from machineconfig.scripts.python.helpers_cloud.helpers2 import ES
|
|
16
|
-
from machineconfig.utils.accessories import pprint
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
console = Console()
|
|
9
|
+
from typing import Annotated
|
|
20
10
|
|
|
21
11
|
|
|
22
12
|
def ftpx(
|
|
@@ -25,7 +15,41 @@ def ftpx(
|
|
|
25
15
|
recursive: Annotated[bool, typer.Option("--recursive", "-r", help="Send recursively.")] = False,
|
|
26
16
|
zipFirst: Annotated[bool, typer.Option("--zipFirst", "-z", help="Zip before sending.")] = False,
|
|
27
17
|
cloud: Annotated[bool, typer.Option("--cloud", "-c", help="Transfer through the cloud.")] = False,
|
|
18
|
+
overwrite_existing: Annotated[bool, typer.Option("--overwrite-existing", "-o", help="Overwrite existing files on remote when sending from local to remote.")] = False,
|
|
28
19
|
) -> None:
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
if target == "wsl" or source == "wsl":
|
|
22
|
+
from machineconfig.utils.ssh_utils.wsl import copy_when_inside_windows
|
|
23
|
+
if target == "wsl":
|
|
24
|
+
target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
|
|
25
|
+
source_obj = target_obj
|
|
26
|
+
else:
|
|
27
|
+
source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
|
|
28
|
+
target_obj = source_obj
|
|
29
|
+
copy_when_inside_windows(source_obj, target_obj, overwrite_existing)
|
|
30
|
+
return
|
|
31
|
+
elif source == "win" or target == "win":
|
|
32
|
+
if source == "win":
|
|
33
|
+
source_obj = Path(target).expanduser().absolute().relative_to(Path.home())
|
|
34
|
+
target_obj = source_obj
|
|
35
|
+
else:
|
|
36
|
+
target_obj = Path(source).expanduser().absolute().relative_to(Path.home())
|
|
37
|
+
source_obj = target_obj
|
|
38
|
+
from machineconfig.utils.ssh_utils.wsl import copy_when_inside_wsl
|
|
39
|
+
copy_when_inside_wsl(source_obj, target_obj, overwrite_existing)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
from rich.console import Console
|
|
43
|
+
from rich.panel import Panel
|
|
44
|
+
|
|
45
|
+
from machineconfig.utils.ssh import SSH
|
|
46
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
47
|
+
from machineconfig.scripts.python.helpers_cloud.helpers2 import ES
|
|
48
|
+
from machineconfig.utils.accessories import pprint
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
console = Console()
|
|
52
|
+
|
|
29
53
|
console.print(
|
|
30
54
|
Panel(
|
|
31
55
|
"\n".join(
|
|
@@ -185,7 +209,7 @@ def ftpx(
|
|
|
185
209
|
padding=(1, 2),
|
|
186
210
|
)
|
|
187
211
|
)
|
|
188
|
-
received_file = ssh.copy_from_here(source_path=resolved_source, target_rel2home=resolved_target, compress_with_zip=zipFirst, recursive=recursive, overwrite_existing=
|
|
212
|
+
received_file = ssh.copy_from_here(source_path=resolved_source, target_rel2home=resolved_target, compress_with_zip=zipFirst, recursive=recursive, overwrite_existing=overwrite_existing)
|
|
189
213
|
|
|
190
214
|
if source_is_remote and isinstance(received_file, PathExtended):
|
|
191
215
|
console.print(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
import ast
|
|
3
|
+
import os
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SymbolInfo(TypedDict):
|
|
8
|
+
"""Represents a symbol (module, class, or function) in the repository."""
|
|
9
|
+
type: str
|
|
10
|
+
name: str
|
|
11
|
+
path: str
|
|
12
|
+
# line: int | None
|
|
13
|
+
# column: int | None
|
|
14
|
+
docstring: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_docstring(node: ast.AsyncFunctionDef | ast.FunctionDef | ast.ClassDef | ast.Module) -> str:
|
|
18
|
+
"""Extract docstring from an AST node."""
|
|
19
|
+
return ast.get_docstring(node) or ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _extract_symbols(tree: ast.AST, module_path: str, source: str) -> list[SymbolInfo]:
|
|
23
|
+
"""Extract symbols from an AST tree."""
|
|
24
|
+
symbols: list[SymbolInfo] = []
|
|
25
|
+
|
|
26
|
+
for node in ast.walk(tree):
|
|
27
|
+
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
28
|
+
symbol: SymbolInfo = {
|
|
29
|
+
"type": "function",
|
|
30
|
+
"name": node.name,
|
|
31
|
+
"path": f"{module_path}.{node.name}",
|
|
32
|
+
"docstring": _get_docstring(node),
|
|
33
|
+
}
|
|
34
|
+
symbols.append(symbol)
|
|
35
|
+
elif isinstance(node, ast.ClassDef):
|
|
36
|
+
symbol: SymbolInfo = {
|
|
37
|
+
"type": "class",
|
|
38
|
+
"name": node.name,
|
|
39
|
+
"path": f"{module_path}.{node.name}",
|
|
40
|
+
"docstring": _get_docstring(node),
|
|
41
|
+
}
|
|
42
|
+
symbols.append(symbol)
|
|
43
|
+
|
|
44
|
+
return symbols
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_repo_symbols(repo_path: str) -> list[SymbolInfo]:
|
|
48
|
+
skip_dirs = {'.venv', 'venv', '__pycache__', '.mypy_cache', '.pytest_cache', '.git'}
|
|
49
|
+
results: list[SymbolInfo] = []
|
|
50
|
+
counter: int = 0
|
|
51
|
+
for root, dirs, files in os.walk(repo_path):
|
|
52
|
+
dirs[:] = [d for d in dirs if d not in skip_dirs and not d.startswith('.')]
|
|
53
|
+
for file in files:
|
|
54
|
+
if not file.endswith(".py"):
|
|
55
|
+
continue
|
|
56
|
+
file_path = os.path.join(root, file)
|
|
57
|
+
module_path = (
|
|
58
|
+
os.path.relpath(file_path, repo_path)
|
|
59
|
+
.replace(os.sep, ".")
|
|
60
|
+
.removesuffix(".py")
|
|
61
|
+
)
|
|
62
|
+
try:
|
|
63
|
+
if counter % 100 == 0: print(f"🔍 Parsing {counter}: {file_path}...")
|
|
64
|
+
with open(file_path, encoding="utf-8") as f:
|
|
65
|
+
source = f.read()
|
|
66
|
+
tree = ast.parse(source, filename=file_path)
|
|
67
|
+
symbols = _extract_symbols(tree, module_path, source)
|
|
68
|
+
results.extend(symbols)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"⚠️ Error parsing {file_path}: {e}")
|
|
71
|
+
continue
|
|
72
|
+
counter += 1
|
|
73
|
+
|
|
74
|
+
return results
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_qrcode_grid(
|
|
7
|
+
strings: list[str],
|
|
8
|
+
output_path: str,
|
|
9
|
+
per_row: int = 3,
|
|
10
|
+
qr_size: int = 200,
|
|
11
|
+
label_height: int = 30,
|
|
12
|
+
padding: int = 20,
|
|
13
|
+
label_max_chars: int = 25,
|
|
14
|
+
format: Literal["svg", "png"] = "svg",
|
|
15
|
+
) -> str:
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
if not strings:
|
|
18
|
+
raise ValueError("strings list cannot be empty")
|
|
19
|
+
|
|
20
|
+
output_path_obj = Path(output_path)
|
|
21
|
+
output_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
if format == "svg":
|
|
24
|
+
return _generate_svg(strings, output_path, per_row, qr_size, label_height, padding, label_max_chars)
|
|
25
|
+
elif format == "png":
|
|
26
|
+
return _generate_png(strings, output_path, per_row, qr_size, label_height, padding, label_max_chars)
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _generate_svg(
|
|
32
|
+
strings: list[str],
|
|
33
|
+
output_path: str,
|
|
34
|
+
per_row: int,
|
|
35
|
+
qr_size: int,
|
|
36
|
+
label_height: int,
|
|
37
|
+
padding: int,
|
|
38
|
+
label_max_chars: int,
|
|
39
|
+
) -> str:
|
|
40
|
+
num_items = len(strings)
|
|
41
|
+
num_rows = (num_items + per_row - 1) // per_row
|
|
42
|
+
|
|
43
|
+
cell_width = qr_size
|
|
44
|
+
cell_height = qr_size + label_height
|
|
45
|
+
total_width = per_row * cell_width + (per_row + 1) * padding
|
|
46
|
+
total_height = num_rows * cell_height + (num_rows + 1) * padding
|
|
47
|
+
|
|
48
|
+
from xml.etree import ElementTree as ET
|
|
49
|
+
|
|
50
|
+
import qrcode
|
|
51
|
+
svg_root = ET.Element(
|
|
52
|
+
"svg",
|
|
53
|
+
xmlns="http://www.w3.org/2000/svg",
|
|
54
|
+
width=str(total_width),
|
|
55
|
+
height=str(total_height),
|
|
56
|
+
viewBox=f"0 0 {total_width} {total_height}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
_bg_rect = ET.SubElement(svg_root, "rect", width=str(total_width), height=str(total_height), fill="white")
|
|
60
|
+
|
|
61
|
+
for idx, text in enumerate(strings):
|
|
62
|
+
row = idx // per_row
|
|
63
|
+
col = idx % per_row
|
|
64
|
+
|
|
65
|
+
x_offset = padding + col * (cell_width + padding)
|
|
66
|
+
y_offset = padding + row * (cell_height + padding)
|
|
67
|
+
|
|
68
|
+
qr = qrcode.QRCode(version=1, error_correction=qrcode.ERROR_CORRECT_L, box_size=10, border=2, image_factory=qrcode.image.svg.SvgPathImage) # type: ignore
|
|
69
|
+
qr.add_data(text)
|
|
70
|
+
qr.make(fit=True)
|
|
71
|
+
|
|
72
|
+
qr_img = qr.make_image()
|
|
73
|
+
qr_svg_string = qr_img.to_string(encoding="unicode")
|
|
74
|
+
|
|
75
|
+
qr_tree = ET.fromstring(qr_svg_string)
|
|
76
|
+
|
|
77
|
+
group = ET.SubElement(svg_root, "g", transform=f"translate({x_offset}, {y_offset})")
|
|
78
|
+
|
|
79
|
+
qr_group = ET.SubElement(group, "g")
|
|
80
|
+
for child in qr_tree:
|
|
81
|
+
qr_group.append(child)
|
|
82
|
+
|
|
83
|
+
label_text = text[:label_max_chars] if len(text) > label_max_chars else text
|
|
84
|
+
text_y = qr_size + label_height // 2
|
|
85
|
+
|
|
86
|
+
text_elem = ET.SubElement(
|
|
87
|
+
group,
|
|
88
|
+
"text",
|
|
89
|
+
x=str(qr_size // 2),
|
|
90
|
+
y=str(text_y),
|
|
91
|
+
fill="black",
|
|
92
|
+
attrib={
|
|
93
|
+
"font-family": "monospace",
|
|
94
|
+
"font-size": "12",
|
|
95
|
+
"text-anchor": "middle",
|
|
96
|
+
"dominant-baseline": "middle",
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
text_elem.text = label_text
|
|
100
|
+
|
|
101
|
+
tree = ET.ElementTree(svg_root)
|
|
102
|
+
ET.indent(tree, space=" ")
|
|
103
|
+
tree.write(output_path, encoding="unicode", xml_declaration=True)
|
|
104
|
+
|
|
105
|
+
return output_path
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _generate_png(
|
|
109
|
+
strings: list[str],
|
|
110
|
+
output_path: str,
|
|
111
|
+
per_row: int,
|
|
112
|
+
qr_size: int,
|
|
113
|
+
label_height: int,
|
|
114
|
+
padding: int,
|
|
115
|
+
label_max_chars: int,
|
|
116
|
+
) -> str:
|
|
117
|
+
num_items = len(strings)
|
|
118
|
+
num_rows = (num_items + per_row - 1) // per_row
|
|
119
|
+
|
|
120
|
+
cell_width = qr_size
|
|
121
|
+
cell_height = qr_size + label_height
|
|
122
|
+
total_width = per_row * cell_width + (per_row + 1) * padding
|
|
123
|
+
total_height = num_rows * cell_height + (num_rows + 1) * padding
|
|
124
|
+
|
|
125
|
+
import qrcode
|
|
126
|
+
import qrcode.image.pil
|
|
127
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
128
|
+
img = Image.new("RGB", (total_width, total_height), color="white")
|
|
129
|
+
draw = ImageDraw.Draw(img)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", 12)
|
|
133
|
+
except OSError:
|
|
134
|
+
try:
|
|
135
|
+
font = ImageFont.truetype("arial.ttf", 12)
|
|
136
|
+
except OSError:
|
|
137
|
+
font = ImageFont.load_default()
|
|
138
|
+
|
|
139
|
+
for idx, text in enumerate(strings):
|
|
140
|
+
row = idx // per_row
|
|
141
|
+
col = idx % per_row
|
|
142
|
+
|
|
143
|
+
x_offset = padding + col * (cell_width + padding)
|
|
144
|
+
y_offset = padding + row * (cell_height + padding)
|
|
145
|
+
|
|
146
|
+
qr = qrcode.QRCode(version=1, error_correction=qrcode.ERROR_CORRECT_L, box_size=10, border=2, image_factory=qrcode.image.pil.PilImage)
|
|
147
|
+
qr.add_data(text)
|
|
148
|
+
qr.make(fit=True)
|
|
149
|
+
|
|
150
|
+
qr_img = qr.make_image(fill_color="black", back_color="white")
|
|
151
|
+
qr_img_resized = qr_img.resize((qr_size, qr_size), Image.Resampling.LANCZOS)
|
|
152
|
+
|
|
153
|
+
img.paste(qr_img_resized, (x_offset, y_offset))
|
|
154
|
+
|
|
155
|
+
label_text = text[:label_max_chars] if len(text) > label_max_chars else text
|
|
156
|
+
|
|
157
|
+
bbox = draw.textbbox((0, 0), label_text, font=font)
|
|
158
|
+
text_width = bbox[2] - bbox[0]
|
|
159
|
+
text_x = x_offset + (qr_size - text_width) // 2
|
|
160
|
+
text_y = y_offset + qr_size + label_height // 2 - 6
|
|
161
|
+
|
|
162
|
+
draw.text((text_x, text_y), label_text, fill="black", font=font)
|
|
163
|
+
|
|
164
|
+
img.save(output_path, format="PNG")
|
|
165
|
+
|
|
166
|
+
return output_path
|