machineconfig 2.1__py3-none-any.whl → 2.3__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/enhanced_command_runner.py +0 -2
- machineconfig/cluster/sessions_managers/layout_types.py +29 -0
- machineconfig/cluster/sessions_managers/wt_local.py +68 -62
- machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
- machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
- machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
- machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
- machineconfig/cluster/templates/utils.py +0 -35
- machineconfig/jobs/python/check_installations.py +1 -1
- machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
- machineconfig/jobs/python_generic_installers/config.json +1 -1
- machineconfig/profile/create.py +13 -4
- machineconfig/profile/create_hardlinks.py +3 -1
- machineconfig/profile/shell.py +8 -7
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/linux/devops +6 -4
- machineconfig/scripts/python/ai/generate_files.py +14 -15
- machineconfig/scripts/python/ai/mcinit.py +8 -5
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +7 -7
- machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
- machineconfig/scripts/python/cloud_copy.py +22 -13
- machineconfig/scripts/python/cloud_mount.py +35 -23
- machineconfig/scripts/python/cloud_repo_sync.py +38 -25
- machineconfig/scripts/python/cloud_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +37 -28
- machineconfig/scripts/python/devops.py +46 -27
- machineconfig/scripts/python/devops_add_identity.py +15 -25
- machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
- machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
- machineconfig/scripts/python/devops_devapps_install.py +26 -20
- machineconfig/scripts/python/devops_update_repos.py +142 -57
- machineconfig/scripts/python/dotfile.py +16 -14
- machineconfig/scripts/python/fire_agents.py +30 -23
- machineconfig/scripts/python/fire_jobs.py +86 -98
- machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
- machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
- machineconfig/scripts/python/ftpx.py +24 -14
- machineconfig/scripts/python/get_zellij_cmd.py +8 -7
- machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
- machineconfig/scripts/python/helpers/helpers2.py +25 -14
- machineconfig/scripts/python/helpers/helpers4.py +44 -31
- machineconfig/scripts/python/helpers/helpers5.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
- machineconfig/scripts/python/mount_nfs.py +8 -15
- machineconfig/scripts/python/mount_nw_drive.py +10 -5
- machineconfig/scripts/python/mount_ssh.py +8 -6
- machineconfig/scripts/python/repos.py +215 -57
- machineconfig/scripts/python/snapshot.py +0 -1
- machineconfig/scripts/python/start_slidev.py +10 -5
- machineconfig/scripts/python/start_terminals.py +22 -16
- machineconfig/scripts/python/viewer_template.py +0 -1
- machineconfig/scripts/python/wifi_conn.py +49 -76
- machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
- machineconfig/settings/lf/linux/lfrc +1 -0
- machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
- machineconfig/utils/code.py +2 -3
- machineconfig/utils/installer.py +2 -2
- machineconfig/utils/installer_utils/installer_abc.py +2 -4
- machineconfig/utils/installer_utils/installer_class.py +6 -4
- machineconfig/utils/links.py +103 -33
- machineconfig/utils/notifications.py +52 -38
- machineconfig/utils/options.py +14 -21
- machineconfig/utils/path.py +12 -12
- machineconfig/utils/path_reduced.py +239 -200
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +9 -19
- machineconfig/utils/terminal.py +4 -2
- machineconfig/utils/upgrade_packages.py +91 -0
- machineconfig/utils/utils2.py +1 -2
- machineconfig/utils/utils5.py +23 -11
- machineconfig/utils/ve.py +4 -1
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
- machineconfig-2.3.dist-info/entry_points.txt +2 -0
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
- machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
- machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
- machineconfig/utils/utils.py +0 -97
- /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
- /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
- /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
- /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
- /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
- /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
- /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
- /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
- /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
- /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
- /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
"""Utility to launch multiple AI agent prompts in a Zellij session.
|
|
3
2
|
|
|
4
3
|
Improved design notes:
|
|
@@ -16,19 +15,21 @@ from math import ceil
|
|
|
16
15
|
from typing import Literal, TypeAlias, get_args, Iterable
|
|
17
16
|
|
|
18
17
|
from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
|
|
18
|
+
from machineconfig.cluster.sessions_managers.layout_types import TabConfig, LayoutConfig
|
|
19
19
|
from machineconfig.utils.utils2 import randstr
|
|
20
20
|
import random
|
|
21
21
|
# import time
|
|
22
22
|
|
|
23
|
-
AGENTS: TypeAlias = Literal[
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
AGENTS: TypeAlias = Literal[
|
|
24
|
+
"cursor-agent", "gemini", "crush", "q", "onlyPrepPromptFiles"
|
|
25
|
+
# warp terminal
|
|
26
|
+
]
|
|
27
27
|
DEFAULT_AGENT_CAP = 6
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def get_gemini_api_keys() -> list[str]:
|
|
31
31
|
from machineconfig.utils.utils2 import read_ini
|
|
32
|
+
|
|
32
33
|
config = read_ini(Path.home().joinpath("dotfiles/creds/llm/gemini/api_keys.ini"))
|
|
33
34
|
res: list[str] = []
|
|
34
35
|
for a_section_name in list(config.sections()):
|
|
@@ -45,12 +46,17 @@ def get_gemini_api_keys() -> list[str]:
|
|
|
45
46
|
def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
|
|
46
47
|
"""Return all Python files under repo_root whose text contains keyword.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
Notes:
|
|
50
|
+
- Skips any paths that reside under a directory named ".venv" at any depth.
|
|
51
|
+
- Errors reading individual files are ignored (decoded with 'ignore').
|
|
49
52
|
"""
|
|
50
53
|
py_files = list(repo_root.rglob("*.py"))
|
|
51
54
|
keyword_lower = keyword.lower()
|
|
52
55
|
matches: list[Path] = []
|
|
53
56
|
for f in py_files:
|
|
57
|
+
# Skip anything under a .venv directory anywhere in the path
|
|
58
|
+
if any(part == ".venv" for part in f.parts):
|
|
59
|
+
continue
|
|
54
60
|
try:
|
|
55
61
|
if keyword_lower in f.read_text(encoding="utf-8", errors="ignore").lower():
|
|
56
62
|
matches.append(f)
|
|
@@ -63,6 +69,8 @@ def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
|
|
|
63
69
|
def _write_list_file(target: Path, files: Iterable[Path]) -> None:
|
|
64
70
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
65
71
|
target.write_text("\n".join(str(f) for f in files), encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
|
|
66
74
|
def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
|
|
67
75
|
prompts = [p for p in prompts if p.strip() != ""] # drop blank entries
|
|
68
76
|
if len(prompts) <= max_agents:
|
|
@@ -73,6 +81,8 @@ def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
|
|
|
73
81
|
for i in range(0, len(prompts), chunk_size):
|
|
74
82
|
grouped.append("\nTargeted Locations:\n".join(prompts[i : i + chunk_size]))
|
|
75
83
|
return grouped
|
|
84
|
+
|
|
85
|
+
|
|
76
86
|
def _confirm(message: str, default_no: bool = True) -> bool:
|
|
77
87
|
suffix = "[y/N]" if default_no else "[Y/n]"
|
|
78
88
|
answer = input(f"{message} {suffix} ").strip().lower()
|
|
@@ -83,7 +93,7 @@ def _confirm(message: str, default_no: bool = True) -> bool:
|
|
|
83
93
|
return False
|
|
84
94
|
|
|
85
95
|
|
|
86
|
-
def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_agents: int = DEFAULT_AGENT_CAP) -> TabConfig:
|
|
96
|
+
def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_agents: int = DEFAULT_AGENT_CAP) -> list[TabConfig]:
|
|
87
97
|
"""Create tab configuration for a set of agent prompts.
|
|
88
98
|
|
|
89
99
|
If number of prompts exceeds max_agents, ask user for confirmation.
|
|
@@ -93,16 +103,12 @@ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_age
|
|
|
93
103
|
raise ValueError("No prompts provided")
|
|
94
104
|
|
|
95
105
|
if len(prompts) > max_agents:
|
|
96
|
-
proceed = _confirm(
|
|
97
|
-
message=(
|
|
98
|
-
f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"
|
|
99
|
-
)
|
|
100
|
-
)
|
|
106
|
+
proceed = _confirm(message=(f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"))
|
|
101
107
|
if not proceed:
|
|
102
108
|
print("Aborting per user choice.")
|
|
103
|
-
return
|
|
109
|
+
return []
|
|
104
110
|
|
|
105
|
-
tab_config: TabConfig =
|
|
111
|
+
tab_config: list[TabConfig] = []
|
|
106
112
|
tmp_dir = repo_root / ".ai" / f"tmp_prompts/{randstr()}"
|
|
107
113
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
108
114
|
|
|
@@ -145,6 +151,10 @@ crush run {prompt_path}
|
|
|
145
151
|
case "q":
|
|
146
152
|
cmd = f"""
|
|
147
153
|
q chat --no-interactive --trust-all-tools {prompt_path}
|
|
154
|
+
"""
|
|
155
|
+
case "onlyPrepPromptFiles":
|
|
156
|
+
cmd = f"""
|
|
157
|
+
echo "Prepared prompt file at {prompt_path}"
|
|
148
158
|
"""
|
|
149
159
|
case _:
|
|
150
160
|
raise ValueError(f"Unsupported agent type: {agent}")
|
|
@@ -161,9 +171,9 @@ sleep 0.1
|
|
|
161
171
|
sleep 0.1
|
|
162
172
|
echo "---------END OF AGENT OUTPUT---------"
|
|
163
173
|
"""
|
|
164
|
-
cmd_path.write_text(cmd_prefix+cmd+cmd_postfix, encoding="utf-8")
|
|
174
|
+
cmd_path.write_text(cmd_prefix + cmd + cmd_postfix, encoding="utf-8")
|
|
165
175
|
fire_cmd = f"bash {shlex.quote(str(cmd_path))}"
|
|
166
|
-
tab_config
|
|
176
|
+
tab_config.append(TabConfig(tabName=f"Agent{idx}", startDir=str(repo_root), command=fire_cmd))
|
|
167
177
|
|
|
168
178
|
print(f"Launching a template with #{len(tab_config)} agents")
|
|
169
179
|
return tab_config
|
|
@@ -208,20 +218,17 @@ def main(): # noqa: C901 - (complexity acceptable for CLI glue)
|
|
|
208
218
|
combined_prompts = [prefix + "\n" + p for p in combined_prompts]
|
|
209
219
|
|
|
210
220
|
from machineconfig.utils.options import choose_one_option
|
|
221
|
+
|
|
211
222
|
agent_selected = choose_one_option(header="Select agent type", options=get_args(AGENTS))
|
|
212
223
|
|
|
213
|
-
tab_config = launch_agents(
|
|
214
|
-
repo_root=repo_root,
|
|
215
|
-
prompts=combined_prompts,
|
|
216
|
-
agent=agent_selected,
|
|
217
|
-
max_agents=DEFAULT_AGENT_CAP,
|
|
218
|
-
)
|
|
224
|
+
tab_config = launch_agents(repo_root=repo_root, prompts=combined_prompts, agent=agent_selected, max_agents=DEFAULT_AGENT_CAP)
|
|
219
225
|
if not tab_config:
|
|
220
226
|
return
|
|
221
227
|
|
|
222
228
|
from machineconfig.utils.utils2 import randstr
|
|
229
|
+
|
|
223
230
|
random_name = randstr(length=3)
|
|
224
|
-
manager = ZellijLocalManager(
|
|
231
|
+
manager = ZellijLocalManager(session_layouts=[LayoutConfig(layoutName="Agents", layoutTabs=tab_config)], session_name_prefix=random_name)
|
|
225
232
|
manager.start_all_sessions()
|
|
226
233
|
manager.run_monitoring_routine()
|
|
227
234
|
|
|
@@ -7,61 +7,34 @@ fire
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
|
|
12
11
|
from machineconfig.scripts.python.helpers.helpers4 import convert_kwargs_to_fire_kwargs_str
|
|
13
12
|
from machineconfig.scripts.python.helpers.helpers4 import parse_pyfile
|
|
14
13
|
from machineconfig.scripts.python.helpers.helpers4 import get_import_module_code
|
|
15
14
|
from machineconfig.utils.ve import get_repo_root, get_ve_activate_line, get_ve_path_and_ipython_profile
|
|
16
|
-
from machineconfig.utils.
|
|
15
|
+
from machineconfig.utils.options import display_options, choose_one_option
|
|
16
|
+
from machineconfig.utils.path import match_file_name, sanitize_path
|
|
17
|
+
from machineconfig.utils.source_of_truth import PROGRAM_PATH
|
|
17
18
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
18
19
|
from machineconfig.utils.io_save import save_toml
|
|
19
20
|
from machineconfig.utils.utils2 import randstr, read_toml
|
|
21
|
+
from machineconfig.scripts.python.fire_jobs_args_helper import get_args, FireJobArgs, extract_kwargs
|
|
20
22
|
import platform
|
|
21
23
|
from typing import Optional
|
|
22
|
-
import argparse
|
|
23
24
|
import os
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
def main(args: FireJobArgs) -> None:
|
|
28
|
+
if args.layout:
|
|
29
|
+
from machineconfig.scripts.python.fire_jobs_layout_helper import handle_layout_args
|
|
27
30
|
|
|
31
|
+
return handle_layout_args(args)
|
|
28
32
|
|
|
29
|
-
def main() -> None:
|
|
30
|
-
parser = argparse.ArgumentParser()
|
|
31
|
-
parser.add_argument("path", nargs='?', type=str, help="The directory containing the jobs", default=".")
|
|
32
|
-
parser.add_argument("function", nargs='?', type=str, help="Fuction to run", default=None)
|
|
33
|
-
parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
|
|
34
|
-
parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
|
|
35
|
-
parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
|
|
36
|
-
parser.add_argument("--debug", "-d", action="store_true", help="debug")
|
|
37
|
-
parser.add_argument("--choose_function", "-c", action="store_true", help="debug")
|
|
38
|
-
parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion/interruption)")
|
|
39
|
-
parser.add_argument("--jupyter", "-j", action="store_true", help="open in a jupyter notebook")
|
|
40
|
-
parser.add_argument("--submit_to_cloud", "-C", action="store_true", help="submit to cloud compute")
|
|
41
|
-
parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
|
|
42
|
-
parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
|
|
43
|
-
parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
|
|
44
|
-
parser.add_argument("--environment", "-E", type=str, help="Choose ip, localhost, hostname or arbitrary url", default="")
|
|
45
|
-
parser.add_argument("--holdDirectory", "-D", action="store_true", help="hold current directory and avoid cd'ing to the script directory")
|
|
46
|
-
parser.add_argument("--PathExport", "-P", action="store_true", help="augment the PYTHONPATH with repo root.")
|
|
47
|
-
parser.add_argument("--git_pull", "-g", action="store_true", help="Start by pulling the git repo")
|
|
48
|
-
parser.add_argument("--optimized", "-O", action="store_true", help="Run the optimized version of the function")
|
|
49
|
-
parser.add_argument("--Nprocess", "-p", type=int, help="Number of processes to use", default=1)
|
|
50
|
-
parser.add_argument("--zellij_tab", "-z", type=str, dest="zellij_tab", help="open in a new zellij tab")
|
|
51
|
-
parser.add_argument("--watch", "-w", action="store_true", help="watch the file for changes")
|
|
52
|
-
parser.add_argument("--kw", nargs="*", default=None, help="keyword arguments to pass to the function in the form of k1 v1 k2 v2 ... (meaning k1=v1, k2=v2, etc)")
|
|
53
|
-
try:
|
|
54
|
-
args = parser.parse_args()
|
|
55
|
-
except Exception as ex:
|
|
56
|
-
print(f"❌ Failed to parse arguments: {ex}")
|
|
57
|
-
parser.print_help()
|
|
58
|
-
raise ex
|
|
59
33
|
path_obj = sanitize_path(args.path)
|
|
60
|
-
# print(f"Passed path sanitied to {path_obj}")
|
|
61
34
|
if not path_obj.exists():
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
35
|
+
suffixes = {".py", ".sh", ".ps1"}
|
|
36
|
+
choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes=suffixes)
|
|
37
|
+
elif path_obj.is_dir():
|
|
65
38
|
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
66
39
|
files = search_for_files_of_interest(path_obj)
|
|
67
40
|
print(f"🔍 Got #{len(files)} results.")
|
|
@@ -71,23 +44,13 @@ def main() -> None:
|
|
|
71
44
|
choice_file = path_obj
|
|
72
45
|
repo_root = get_repo_root(str(choice_file))
|
|
73
46
|
print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
|
|
74
|
-
|
|
75
47
|
ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
|
|
76
|
-
if ipy_profile is None:
|
|
77
|
-
|
|
48
|
+
if ipy_profile is None:
|
|
49
|
+
ipy_profile = "default"
|
|
50
|
+
activate_ve_line = get_ve_activate_line(ve_root=args.ve or ve_root_from_file or "$HOME/code/machineconfig/.venv")
|
|
78
51
|
|
|
79
|
-
# Convert args.kw to dictionary
|
|
80
52
|
if choice_file.suffix == ".py":
|
|
81
|
-
|
|
82
|
-
assert len(args.kw) % 2 == 0, f"args.kw must be a list of even length. Got {len(args.kw)}"
|
|
83
|
-
kwargs = dict(zip(args.kw[::2], args.kw[1::2]))
|
|
84
|
-
for key, value in kwargs.items():
|
|
85
|
-
if value in str2obj:
|
|
86
|
-
kwargs[key] = str2obj[value]
|
|
87
|
-
if args.function is None: # if user passed arguments and forgot to pass function, then assume they want to run the main function.
|
|
88
|
-
args.choose_function = True
|
|
89
|
-
else:
|
|
90
|
-
kwargs = {}
|
|
53
|
+
kwargs = extract_kwargs(args)
|
|
91
54
|
else:
|
|
92
55
|
kwargs = {}
|
|
93
56
|
|
|
@@ -99,19 +62,23 @@ def main() -> None:
|
|
|
99
62
|
choice_function_tmp = display_options(msg="Choose a function to run", options=options, fzf=True, multi=False)
|
|
100
63
|
assert isinstance(choice_function_tmp, str), f"choice_function must be a string. Got {type(choice_function_tmp)}"
|
|
101
64
|
choice_index = options.index(choice_function_tmp)
|
|
102
|
-
choice_function = choice_function_tmp.split(
|
|
65
|
+
choice_function = choice_function_tmp.split(" -- ")[0]
|
|
103
66
|
choice_function_args = func_args[choice_index]
|
|
104
67
|
|
|
105
|
-
if choice_function == "RUN AS MAIN":
|
|
68
|
+
if choice_function == "RUN AS MAIN":
|
|
69
|
+
choice_function = None
|
|
106
70
|
if len(choice_function_args) > 0 and len(kwargs) == 0:
|
|
107
71
|
for item in choice_function_args:
|
|
108
72
|
kwargs[item.name] = input(f"Please enter a value for argument `{item.name}` (type = {item.type}) (default = {item.default}) : ") or item.default
|
|
109
73
|
elif choice_file.suffix == ".sh": # in this case, we choos lines.
|
|
110
74
|
options = []
|
|
111
75
|
for line in choice_file.read_text(encoding="utf-8").splitlines():
|
|
112
|
-
if line.startswith("#"):
|
|
113
|
-
|
|
114
|
-
if line
|
|
76
|
+
if line.startswith("#"):
|
|
77
|
+
continue
|
|
78
|
+
if line == "":
|
|
79
|
+
continue
|
|
80
|
+
if line.startswith("echo"):
|
|
81
|
+
continue
|
|
115
82
|
options.append(line)
|
|
116
83
|
chosen_lines = display_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
|
|
117
84
|
choice_file = PathExtended.tmpfile(suffix=".sh")
|
|
@@ -124,9 +91,10 @@ def main() -> None:
|
|
|
124
91
|
if choice_file.suffix == ".py":
|
|
125
92
|
if args.streamlit:
|
|
126
93
|
import socket
|
|
94
|
+
|
|
127
95
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
128
96
|
try:
|
|
129
|
-
s.connect((
|
|
97
|
+
s.connect(("8.8.8.8", 1))
|
|
130
98
|
local_ip_v4 = s.getsockname()[0]
|
|
131
99
|
except Exception:
|
|
132
100
|
local_ip_v4 = socket.gethostbyname(socket.gethostname())
|
|
@@ -140,7 +108,8 @@ def main() -> None:
|
|
|
140
108
|
toml_path = toml_path_maybe
|
|
141
109
|
elif choice_file.parent.name == "pages":
|
|
142
110
|
toml_path_maybe = choice_file.parent.parent.joinpath(".streamlit/config.toml")
|
|
143
|
-
if toml_path_maybe.exists():
|
|
111
|
+
if toml_path_maybe.exists():
|
|
112
|
+
toml_path = toml_path_maybe
|
|
144
113
|
if toml_path is not None:
|
|
145
114
|
print(f"📄 Reading config.toml @ {toml_path}")
|
|
146
115
|
config = read_toml(toml_path)
|
|
@@ -152,10 +121,14 @@ def main() -> None:
|
|
|
152
121
|
secrets_template_path = PathExtended.home().joinpath(f"dotfiles/creds/streamlit/{PathExtended(repo_root).name}/{choice_file.name}/secrets.toml")
|
|
153
122
|
if args.environment != "" and not secrets_path.exists() and secrets_template_path.exists():
|
|
154
123
|
secrets_template = read_toml(secrets_template_path)
|
|
155
|
-
if args.environment == "ip":
|
|
156
|
-
|
|
157
|
-
elif args.environment == "
|
|
158
|
-
|
|
124
|
+
if args.environment == "ip":
|
|
125
|
+
host_url = f"http://{local_ip_v4}:{port}/oauth2callback"
|
|
126
|
+
elif args.environment == "localhost":
|
|
127
|
+
host_url = f"http://localhost:{port}/oauth2callback"
|
|
128
|
+
elif args.environment == "hostname":
|
|
129
|
+
host_url = f"http://{computer_name}:{port}/oauth2callback"
|
|
130
|
+
else:
|
|
131
|
+
host_url = f"http://{args.environment}:{port}/oauth2callback"
|
|
159
132
|
try:
|
|
160
133
|
secrets_template["auth"]["redirect_uri"] = host_url
|
|
161
134
|
secrets_template["auth"]["cookie_secret"] = randstr(35)
|
|
@@ -167,11 +140,14 @@ def main() -> None:
|
|
|
167
140
|
message = f"🚀 Streamlit app is running @:\n1- http://{local_ip_v4}:{port}\n2- http://{computer_name}:{port}\n3- http://localhost:{port}"
|
|
168
141
|
from rich.panel import Panel
|
|
169
142
|
from rich import print as rprint
|
|
143
|
+
|
|
170
144
|
rprint(Panel(message))
|
|
171
145
|
exe = f"streamlit run --server.address 0.0.0.0 --server.headless true --server.port {port}"
|
|
172
146
|
# exe = f"cd '{choice_file.parent}'; " + exe
|
|
173
|
-
elif args.interactive is False:
|
|
174
|
-
|
|
147
|
+
elif args.interactive is False:
|
|
148
|
+
exe = "python"
|
|
149
|
+
elif args.jupyter:
|
|
150
|
+
exe = "jupyter-lab"
|
|
175
151
|
else:
|
|
176
152
|
exe = f"ipython -i --no-banner --profile {ipy_profile} "
|
|
177
153
|
elif choice_file.suffix == ".ps1" or choice_file.suffix == ".sh":
|
|
@@ -181,7 +157,9 @@ def main() -> None:
|
|
|
181
157
|
else:
|
|
182
158
|
raise NotImplementedError(f"File type {choice_file.suffix} not supported, in the sense that I don't know how to fire it.")
|
|
183
159
|
|
|
184
|
-
if
|
|
160
|
+
if (
|
|
161
|
+
args.module or (args.debug and args.choose_function)
|
|
162
|
+
): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
|
|
185
163
|
assert choice_file.suffix == ".py", f"File must be a python file to be imported as a module. Got {choice_file}"
|
|
186
164
|
import_line = get_import_module_code(str(choice_file))
|
|
187
165
|
if repo_root is not None:
|
|
@@ -201,11 +179,15 @@ except (ImportError, ModuleNotFoundError) as ex:
|
|
|
201
179
|
print(fr"✅ Successfully imported `{choice_file}`")
|
|
202
180
|
"""
|
|
203
181
|
if choice_function is not None:
|
|
204
|
-
txt =
|
|
205
|
-
|
|
182
|
+
txt = (
|
|
183
|
+
txt
|
|
184
|
+
+ f"""
|
|
185
|
+
res = {choice_function}({("**" + str(kwargs)) if kwargs else ""})
|
|
206
186
|
"""
|
|
187
|
+
)
|
|
207
188
|
|
|
208
|
-
txt =
|
|
189
|
+
txt = (
|
|
190
|
+
f"""
|
|
209
191
|
try:
|
|
210
192
|
from rich.panel import Panel
|
|
211
193
|
from rich.console import Console
|
|
@@ -214,8 +196,10 @@ try:
|
|
|
214
196
|
console.print(Panel(Syntax(code=r'''{txt}''', lexer='python'), title='Import Script'), style="bold red")
|
|
215
197
|
except ImportError as _ex:
|
|
216
198
|
print(r'''{txt}''')
|
|
217
|
-
"""
|
|
218
|
-
|
|
199
|
+
"""
|
|
200
|
+
+ txt
|
|
201
|
+
)
|
|
202
|
+
choice_file = PathExtended.tmp().joinpath(f"tmp_scripts/python/{PathExtended(choice_file).parent.name}_{PathExtended(choice_file).stem}_{randstr()}.py")
|
|
219
203
|
choice_file.parent.mkdir(parents=True, exist_ok=True)
|
|
220
204
|
choice_file.write_text(txt, encoding="utf-8")
|
|
221
205
|
|
|
@@ -225,7 +209,8 @@ except ImportError as _ex:
|
|
|
225
209
|
command = f"{exe} -m ipdb {choice_file} " # pudb is not available on windows machines, use poor man's debugger instead.
|
|
226
210
|
elif platform.system() in ["Linux", "Darwin"]:
|
|
227
211
|
command = f"{exe} -m pudb {choice_file} " # TODO: functions not supported yet in debug mode.
|
|
228
|
-
else:
|
|
212
|
+
else:
|
|
213
|
+
raise NotImplementedError(f"Platform {platform.system()} not supported.")
|
|
229
214
|
elif args.module:
|
|
230
215
|
# both selected function and kwargs are mentioned in the made up script, therefore no need for fire module.
|
|
231
216
|
command = f"{exe} {choice_file} "
|
|
@@ -249,15 +234,19 @@ except ImportError as _ex:
|
|
|
249
234
|
# command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {PathExtended.cwd()}"
|
|
250
235
|
command = f"{exe} {choice_file} "
|
|
251
236
|
if not args.cmd:
|
|
252
|
-
if "ipdb" in command:
|
|
253
|
-
|
|
237
|
+
if "ipdb" in command:
|
|
238
|
+
command = f"pip install ipdb\n{command}"
|
|
239
|
+
if "pudb" in command:
|
|
240
|
+
command = f"pip install pudb\n{command}"
|
|
254
241
|
command = f"{activate_ve_line}\n{command}"
|
|
255
242
|
else:
|
|
256
243
|
# CMD equivalent
|
|
257
|
-
if "ipdb" in command:
|
|
258
|
-
|
|
244
|
+
if "ipdb" in command:
|
|
245
|
+
command = f"pip install ipdb & {command}"
|
|
246
|
+
if "pudb" in command:
|
|
247
|
+
command = f"pip install pudb & {command}"
|
|
259
248
|
new_line = "\n"
|
|
260
|
-
command =
|
|
249
|
+
command = rf"""start cmd -Argument "/k {activate_ve_line.replace(".ps1", ".bat").replace(". ", "")} & {command.replace(new_line, " & ")} " """ # this works from powershell
|
|
261
250
|
|
|
262
251
|
if args.submit_to_cloud:
|
|
263
252
|
command = f"""
|
|
@@ -267,21 +256,13 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
267
256
|
command += f"--function {choice_function} "
|
|
268
257
|
|
|
269
258
|
if args.Nprocess > 1:
|
|
270
|
-
# lines = [f""" zellij action new-tab --name nProcess{randstr(2)}"""]
|
|
271
|
-
# command = command.replace(". activate_ve", ". $HOME/scripts/activate_ve")
|
|
272
|
-
# for an_arg in range(args.Nprocess):
|
|
273
|
-
# sub_command = f"{command} --idx={an_arg} --idx_max={args.Nprocess}"
|
|
274
|
-
# if args.optimized:
|
|
275
|
-
# sub_command = sub_command.replace("python ", "python -OO ")
|
|
276
|
-
# sub_command_path = PathExtended.tmpfile(suffix=".sh").write_text(sub_command, encoding="utf-8")
|
|
277
|
-
# lines.append(f"""zellij action new-pane -- bash {sub_command_path} """)
|
|
278
|
-
# lines.append("sleep 5") # python tends to freeze if you launch instances within 1 microsecond of each other
|
|
279
|
-
# command = "\n".join(lines)
|
|
280
|
-
tab_config = {}
|
|
281
|
-
for an_arg in range(args.Nprocess):
|
|
282
|
-
tab_config[f"tab{an_arg}"] = (str(PathExtended.cwd()), f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={args.Nprocess}")
|
|
283
259
|
from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
|
|
284
|
-
|
|
260
|
+
from machineconfig.cluster.sessions_managers.layout_types import LayoutConfig
|
|
261
|
+
|
|
262
|
+
layout: LayoutConfig = {"layoutName": "fireNprocess", "layoutTabs": []}
|
|
263
|
+
for an_arg in range(args.Nprocess):
|
|
264
|
+
layout["layoutTabs"].append({"tabName": f"tab{an_arg}", "startDir": str(PathExtended.cwd()), "command": f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={args.Nprocess}"})
|
|
265
|
+
run_zellij_layout(layout_config=layout)
|
|
285
266
|
return None
|
|
286
267
|
|
|
287
268
|
if args.optimized:
|
|
@@ -293,6 +274,7 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
293
274
|
from rich.panel import Panel
|
|
294
275
|
from rich.console import Console
|
|
295
276
|
from rich.syntax import Syntax
|
|
277
|
+
|
|
296
278
|
console = Console()
|
|
297
279
|
|
|
298
280
|
if args.zellij_tab is not None:
|
|
@@ -301,17 +283,22 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
301
283
|
comman_path__.write_text(command, encoding="utf-8")
|
|
302
284
|
console.print(Panel(Syntax(command, lexer="shell"), title=f"🔥 fire command @ {comman_path__}: "), style="bold red")
|
|
303
285
|
import subprocess
|
|
286
|
+
|
|
304
287
|
existing_tab_names = subprocess.run(["zellij", "action", "query-tab-names"], capture_output=True, text=True, check=True).stdout.splitlines()
|
|
305
288
|
if args.zellij_tab in existing_tab_names:
|
|
306
289
|
print(f"⚠️ Tab name `{args.zellij_tab}` already exists. Please choose a different name.")
|
|
307
290
|
# args.zellij_tab = input("Please enter a new tab name: ")
|
|
308
291
|
args.zellij_tab += f"_{randstr(3)}"
|
|
309
292
|
from machineconfig.cluster.sessions_managers.zellij_local import run_command_in_zellij_tab
|
|
293
|
+
|
|
310
294
|
command = run_command_in_zellij_tab(command=str(comman_path__), tab_name=args.zellij_tab, cwd=None)
|
|
311
|
-
if args.watch:
|
|
312
|
-
|
|
295
|
+
if args.watch:
|
|
296
|
+
command = "watchexec --restart --exts py,sh,ps1 " + command
|
|
297
|
+
if args.git_pull:
|
|
298
|
+
command = f"\ngit -C {choice_file.parent} pull\n" + command
|
|
313
299
|
if args.PathExport:
|
|
314
|
-
if platform.system() in ["Linux", "Darwin"]:
|
|
300
|
+
if platform.system() in ["Linux", "Darwin"]:
|
|
301
|
+
export_line = f"""export PYTHONPATH="{repo_root}""" + """:${PYTHONPATH}" """
|
|
315
302
|
elif platform.system() == "Windows":
|
|
316
303
|
# export_line = f"""set PYTHONPATH="{repo_root}""" + """:%PYTHONPATH%" """
|
|
317
304
|
# powershell equivalent
|
|
@@ -327,8 +314,8 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
327
314
|
command = command + "\nsleep 0.5"
|
|
328
315
|
elif platform.system() == "Windows":
|
|
329
316
|
# command = command + "timeout 0.5\n"
|
|
330
|
-
#pwsh equivalent
|
|
331
|
-
command ="$ErrorActionPreference = 'SilentlyContinue';\n" + command + "\nStart-Sleep -Seconds 0.5"
|
|
317
|
+
# pwsh equivalent
|
|
318
|
+
command = "$ErrorActionPreference = 'SilentlyContinue';\n" + command + "\nStart-Sleep -Seconds 0.5"
|
|
332
319
|
else:
|
|
333
320
|
raise NotImplementedError(f"Platform {platform.system()} not supported.")
|
|
334
321
|
command = command + f"\n. {program_path}"
|
|
@@ -337,6 +324,7 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
337
324
|
program_path.write_text(command, encoding="utf-8")
|
|
338
325
|
|
|
339
326
|
|
|
340
|
-
if __name__ ==
|
|
327
|
+
if __name__ == "__main__":
|
|
341
328
|
# options, func_args = parse_pyfile(file_path="C:/Users/aalsaf01/code/machineconfig/myresources/crocodile/core.py")
|
|
342
|
-
|
|
329
|
+
args = get_args()
|
|
330
|
+
main(args)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class FireJobArgs:
|
|
8
|
+
"""Type-safe dataclass for fire_jobs command line arguments."""
|
|
9
|
+
|
|
10
|
+
path: str = "."
|
|
11
|
+
function: Optional[str] = None
|
|
12
|
+
ve: str = ""
|
|
13
|
+
cmd: bool = False
|
|
14
|
+
interactive: bool = False
|
|
15
|
+
debug: bool = False
|
|
16
|
+
choose_function: bool = False
|
|
17
|
+
loop: bool = False
|
|
18
|
+
jupyter: bool = False
|
|
19
|
+
submit_to_cloud: bool = False
|
|
20
|
+
remote: bool = False
|
|
21
|
+
module: bool = False
|
|
22
|
+
streamlit: bool = False
|
|
23
|
+
environment: str = ""
|
|
24
|
+
holdDirectory: bool = False
|
|
25
|
+
PathExport: bool = False
|
|
26
|
+
git_pull: bool = False
|
|
27
|
+
optimized: bool = False
|
|
28
|
+
Nprocess: int = 1
|
|
29
|
+
zellij_tab: Optional[str] = None
|
|
30
|
+
watch: bool = False
|
|
31
|
+
kw: Optional[list[str]] = None
|
|
32
|
+
layout: bool = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_args() -> FireJobArgs:
|
|
36
|
+
parser = argparse.ArgumentParser()
|
|
37
|
+
parser.add_argument("path", nargs="?", type=str, help="The directory containing the jobs", default=".")
|
|
38
|
+
parser.add_argument("function", nargs="?", type=str, help="Fuction to run", default=None)
|
|
39
|
+
parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
|
|
40
|
+
parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
|
|
41
|
+
parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
|
|
42
|
+
parser.add_argument("--debug", "-d", action="store_true", help="debug")
|
|
43
|
+
parser.add_argument("--choose_function", "-c", action="store_true", help="debug")
|
|
44
|
+
parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion/interruption)")
|
|
45
|
+
parser.add_argument("--jupyter", "-j", action="store_true", help="open in a jupyter notebook")
|
|
46
|
+
parser.add_argument("--submit_to_cloud", "-C", action="store_true", help="submit to cloud compute")
|
|
47
|
+
parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
|
|
48
|
+
parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
|
|
49
|
+
parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
|
|
50
|
+
parser.add_argument("--environment", "-E", type=str, help="Choose ip, localhost, hostname or arbitrary url", default="")
|
|
51
|
+
parser.add_argument("--holdDirectory", "-D", action="store_true", help="hold current directory and avoid cd'ing to the script directory")
|
|
52
|
+
parser.add_argument("--PathExport", "-P", action="store_true", help="augment the PYTHONPATH with repo root.")
|
|
53
|
+
parser.add_argument("--git_pull", "-g", action="store_true", help="Start by pulling the git repo")
|
|
54
|
+
parser.add_argument("--optimized", "-O", action="store_true", help="Run the optimized version of the function")
|
|
55
|
+
parser.add_argument("--Nprocess", "-p", type=int, help="Number of processes to use", default=1)
|
|
56
|
+
parser.add_argument("--zellij_tab", "-z", type=str, dest="zellij_tab", help="open in a new zellij tab")
|
|
57
|
+
parser.add_argument("--watch", "-w", action="store_true", help="watch the file for changes")
|
|
58
|
+
parser.add_argument("--kw", nargs="*", default=None, help="keyword arguments to pass to the function in the form of k1 v1 k2 v2 ... (meaning k1=v1, k2=v2, etc)")
|
|
59
|
+
parser.add_argument("--layout", "-L", action="store_true", help="use layout configuration (Zellij Or WindowsTerminal)")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
args_raw = parser.parse_args()
|
|
63
|
+
except Exception as ex:
|
|
64
|
+
print(f"❌ Failed to parse arguments: {ex}")
|
|
65
|
+
parser.print_help()
|
|
66
|
+
raise ex
|
|
67
|
+
args = FireJobArgs(**vars(args_raw))
|
|
68
|
+
return args
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
|
|
72
|
+
str2obj = {"True": True, "False": False, "None": None}
|
|
73
|
+
if args.kw is not None:
|
|
74
|
+
assert len(args.kw) % 2 == 0, f"args.kw must be a list of even length. Got {len(args.kw)}"
|
|
75
|
+
kwargs = dict(zip(args.kw[::2], args.kw[1::2]))
|
|
76
|
+
kwargs: dict[str, object]
|
|
77
|
+
for key, value in kwargs.items():
|
|
78
|
+
if value in str2obj:
|
|
79
|
+
kwargs[key] = str2obj[str(value)]
|
|
80
|
+
if args.function is None: # if user passed arguments and forgot to pass function, then assume they want to run the main function.
|
|
81
|
+
args.choose_function = True
|
|
82
|
+
else:
|
|
83
|
+
kwargs = {}
|
|
84
|
+
return kwargs
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from machineconfig.cluster.sessions_managers.layout_types import LayoutConfig, LayoutsFile
|
|
3
|
+
from typing import Optional, TYPE_CHECKING
|
|
4
|
+
from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
|
|
5
|
+
from machineconfig.utils.options import choose_one_option
|
|
6
|
+
from machineconfig.utils.path import match_file_name, sanitize_path
|
|
7
|
+
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def select_layout(layouts_json_file: Path, layout_name: Optional[str]):
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
|
|
17
|
+
if len(layout_file["layouts"]) == 0:
|
|
18
|
+
raise ValueError(f"No layouts found in {layouts_json_file}")
|
|
19
|
+
if layout_name is None:
|
|
20
|
+
options = [layout["layoutName"] for layout in layout_file["layouts"]]
|
|
21
|
+
from machineconfig.utils.options import choose_one_option
|
|
22
|
+
|
|
23
|
+
layout_name = choose_one_option(options=options, prompt="Choose a layout configuration:", fzf=True)
|
|
24
|
+
print(f"Selected layout: {layout_name}")
|
|
25
|
+
layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layout_name), None)
|
|
26
|
+
if layout_chosen is None:
|
|
27
|
+
layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layout_name.lower()), None)
|
|
28
|
+
if layout_chosen is None:
|
|
29
|
+
available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
|
|
30
|
+
raise ValueError(f"Layout '{layout_name}' not found. Available layouts: {available_layouts}")
|
|
31
|
+
return layout_chosen
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def launch_layout(layout_config: LayoutConfig) -> Optional[Exception]:
|
|
35
|
+
import platform
|
|
36
|
+
|
|
37
|
+
if platform.system() == "Linux" or platform.system() == "Darwin":
|
|
38
|
+
print("🧑💻 Launching layout using Zellij terminal multiplexer...")
|
|
39
|
+
from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
|
|
40
|
+
|
|
41
|
+
run_zellij_layout(layout_config=layout_config)
|
|
42
|
+
elif platform.system() == "Windows":
|
|
43
|
+
print("🧑💻 Launching layout using Windows Terminal...")
|
|
44
|
+
from machineconfig.cluster.sessions_managers.wt_local import run_wt_layout
|
|
45
|
+
|
|
46
|
+
run_wt_layout(layout_config=layout_config)
|
|
47
|
+
else:
|
|
48
|
+
print(f"❌ Unsupported platform: {platform.system()}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def handle_layout_args(args: "FireJobArgs") -> None:
|
|
53
|
+
# args.function = args.path
|
|
54
|
+
# args.path = "layout.json"
|
|
55
|
+
path_obj = sanitize_path(args.path)
|
|
56
|
+
if not path_obj.exists():
|
|
57
|
+
choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes={".json"})
|
|
58
|
+
elif path_obj.is_dir():
|
|
59
|
+
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
60
|
+
files = search_for_files_of_interest(path_obj)
|
|
61
|
+
print(f"🔍 Got #{len(files)} results.")
|
|
62
|
+
choice_file = choose_one_option(options=files, fzf=True)
|
|
63
|
+
choice_file = PathExtended(choice_file)
|
|
64
|
+
else:
|
|
65
|
+
choice_file = path_obj
|
|
66
|
+
launch_layout(layout_config=select_layout(layouts_json_file=choice_file, layout_name=args.function))
|