machineconfig 2.0__py3-none-any.whl → 2.1__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/cloud_manager.py +0 -3
- machineconfig/cluster/data_transfer.py +0 -1
- machineconfig/cluster/file_manager.py +0 -1
- machineconfig/cluster/job_params.py +0 -3
- machineconfig/cluster/loader_runner.py +0 -3
- machineconfig/cluster/remote_machine.py +0 -1
- machineconfig/cluster/script_notify_upon_completion.py +0 -1
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +3 -5
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
- machineconfig/cluster/sessions_managers/wt_local.py +36 -110
- machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
- machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
- machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
- machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
- machineconfig/cluster/templates/cli_click.py +0 -1
- machineconfig/cluster/templates/cli_gooey.py +0 -2
- machineconfig/cluster/templates/cli_trogon.py +0 -1
- machineconfig/cluster/templates/run_cloud.py +0 -1
- machineconfig/cluster/templates/run_cluster.py +0 -1
- machineconfig/cluster/templates/run_remote.py +0 -1
- machineconfig/cluster/templates/utils.py +26 -10
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
- machineconfig/jobs/python/check_installations.py +1 -0
- machineconfig/jobs/python/create_bootable_media.py +0 -2
- machineconfig/jobs/python/python_ve_symlink.py +9 -11
- machineconfig/jobs/python/tasks.py +0 -1
- machineconfig/jobs/python/vscode/api.py +5 -5
- machineconfig/jobs/python/vscode/link_ve.py +13 -14
- machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
- machineconfig/jobs/python/vscode/sync_code.py +9 -13
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
- machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
- machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
- machineconfig/jobs/python_custom_installers/dev/code.py +14 -21
- machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
- machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
- machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
- machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
- machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
- machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
- machineconfig/jobs/python_custom_installers/docker.py +12 -21
- machineconfig/jobs/python_custom_installers/gh.py +11 -19
- machineconfig/jobs/python_custom_installers/hx.py +32 -16
- machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
- 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/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
- machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
- machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
- machineconfig/jobs/windows/start_terminal.ps1 +1 -1
- machineconfig/profile/create.py +29 -22
- machineconfig/profile/create_hardlinks.py +26 -19
- machineconfig/profile/shell.py +51 -28
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/cloud/init.sh +2 -2
- machineconfig/scripts/linux/checkout_versions +1 -1
- machineconfig/scripts/linux/choose_wezterm_theme +1 -1
- machineconfig/scripts/linux/cloud_copy +1 -1
- machineconfig/scripts/linux/cloud_manager +1 -1
- machineconfig/scripts/linux/cloud_mount +1 -1
- machineconfig/scripts/linux/cloud_repo_sync +1 -1
- machineconfig/scripts/linux/cloud_sync +1 -1
- machineconfig/scripts/linux/croshell +1 -1
- machineconfig/scripts/linux/devops +4 -6
- machineconfig/scripts/linux/fire +1 -1
- machineconfig/scripts/linux/fire_agents +3 -2
- machineconfig/scripts/linux/ftpx +1 -1
- machineconfig/scripts/linux/gh_models +1 -1
- machineconfig/scripts/linux/kill_process +1 -1
- machineconfig/scripts/linux/mcinit +1 -1
- machineconfig/scripts/linux/repos +1 -1
- machineconfig/scripts/linux/scheduler +1 -1
- machineconfig/scripts/linux/start_slidev +1 -1
- machineconfig/scripts/linux/start_terminals +1 -1
- machineconfig/scripts/linux/url2md +1 -1
- machineconfig/scripts/linux/warp-cli.sh +122 -0
- machineconfig/scripts/linux/wifi_conn +1 -1
- 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/__init__.py +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/ai/generate_files.py +84 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
- machineconfig/scripts/python/ai/mcinit.py +7 -3
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
- machineconfig/scripts/python/cloud_copy.py +1 -1
- machineconfig/scripts/python/cloud_mount.py +1 -1
- machineconfig/scripts/python/cloud_repo_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +5 -3
- machineconfig/scripts/python/devops_add_identity.py +1 -1
- machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
- machineconfig/scripts/python/devops_backup_retrieve.py +1 -1
- machineconfig/scripts/python/devops_update_repos.py +140 -52
- machineconfig/scripts/python/dotfile.py +1 -1
- machineconfig/scripts/python/fire_agents.py +28 -9
- machineconfig/scripts/python/fire_jobs.py +3 -4
- machineconfig/scripts/python/ftpx.py +2 -1
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/helpers2.py +2 -2
- machineconfig/scripts/python/helpers/helpers4.py +1 -2
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
- machineconfig/scripts/python/mount_nfs.py +1 -1
- machineconfig/scripts/python/mount_ssh.py +1 -1
- machineconfig/scripts/python/repos.py +1 -1
- machineconfig/scripts/python/start_slidev.py +1 -1
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/scripts/windows/checkout_version.ps1 +1 -3
- machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
- machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
- machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
- machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
- machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
- machineconfig/scripts/windows/croshell.ps1 +2 -2
- machineconfig/scripts/windows/devops.ps1 +1 -4
- machineconfig/scripts/windows/dotfile.ps1 +1 -3
- machineconfig/scripts/windows/fire.ps1 +1 -1
- machineconfig/scripts/windows/ftpx.ps1 +2 -2
- machineconfig/scripts/windows/gpt.ps1 +1 -1
- machineconfig/scripts/windows/kill_process.ps1 +1 -2
- machineconfig/scripts/windows/mcinit.ps1 +1 -1
- machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
- machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/pomodoro.ps1 +1 -1
- machineconfig/scripts/windows/py2exe.ps1 +1 -3
- machineconfig/scripts/windows/repos.ps1 +1 -1
- machineconfig/scripts/windows/scheduler.ps1 +1 -1
- machineconfig/scripts/windows/snapshot.ps1 +2 -2
- machineconfig/scripts/windows/start_slidev.ps1 +1 -1
- machineconfig/scripts/windows/start_terminals.ps1 +1 -1
- machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
- machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
- machineconfig/settings/lf/linux/lfrc +1 -1
- machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
- machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
- machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
- machineconfig/settings/svim/linux/init.toml +1 -1
- machineconfig/settings/svim/windows/init.toml +1 -1
- machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
- machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
- machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
- machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
- machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +66 -12
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +44 -36
- machineconfig/utils/ai/generate_file_checklist.py +8 -10
- machineconfig/utils/ai/url2md.py +4 -2
- machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
- machineconfig/utils/cloud/onedrive/transaction.py +63 -98
- machineconfig/utils/code.py +60 -39
- machineconfig/utils/installer.py +27 -33
- machineconfig/utils/installer_utils/installer_abc.py +8 -7
- machineconfig/utils/installer_utils/installer_class.py +149 -70
- machineconfig/utils/links.py +22 -11
- machineconfig/utils/notifications.py +197 -0
- machineconfig/utils/options.py +29 -23
- machineconfig/utils/path.py +13 -6
- machineconfig/utils/path_reduced.py +485 -216
- machineconfig/utils/procs.py +47 -41
- machineconfig/utils/scheduling.py +0 -1
- machineconfig/utils/ssh.py +157 -76
- machineconfig/utils/terminal.py +82 -37
- machineconfig/utils/utils.py +12 -10
- machineconfig/utils/utils2.py +38 -48
- machineconfig/utils/utils5.py +183 -116
- machineconfig/utils/ve.py +9 -4
- {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/METADATA +3 -2
- {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/RECORD +200 -217
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
- machineconfig/jobs/python/archive/python_tools.txt +0 -12
- machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/update.py +0 -3
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/linux/activate_ve +0 -87
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/windows/activate_ve.ps1 +0 -54
- {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
- {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/top_level.txt +0 -0
machineconfig/utils/terminal.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
from machineconfig.utils.path_reduced import P, OPLike
|
|
1
|
+
from machineconfig.utils.path_reduced import PathExtended, OPLike
|
|
3
2
|
import subprocess
|
|
4
3
|
from typing import Any, BinaryIO, Optional, Union
|
|
5
4
|
import platform
|
|
@@ -29,48 +28,70 @@ class Response:
|
|
|
29
28
|
resp.output.stderr = cp.stderr
|
|
30
29
|
resp.output.returncode = cp.returncode
|
|
31
30
|
return resp
|
|
31
|
+
|
|
32
32
|
def __init__(self, stdin: Optional[BinaryIO] = None, stdout: Optional[BinaryIO] = None, stderr: Optional[BinaryIO] = None, cmd: Optional[str] = None, desc: str = ""):
|
|
33
33
|
self.std = dict(stdin=stdin, stdout=stdout, stderr=stderr)
|
|
34
34
|
self.output = STD(stdin="", stdout="", stderr="", returncode=0)
|
|
35
35
|
self.input = cmd
|
|
36
36
|
self.desc = desc # input command
|
|
37
|
+
|
|
37
38
|
def __call__(self, *args: Any, **kwargs: Any) -> Optional[str]:
|
|
38
39
|
_ = args, kwargs
|
|
39
40
|
return self.op.rstrip() if type(self.op) is str else None
|
|
41
|
+
|
|
40
42
|
@property
|
|
41
|
-
def op(self) -> str:
|
|
43
|
+
def op(self) -> str:
|
|
44
|
+
return self.output.stdout
|
|
45
|
+
|
|
42
46
|
@property
|
|
43
|
-
def ip(self) -> str:
|
|
47
|
+
def ip(self) -> str:
|
|
48
|
+
return self.output.stdin
|
|
49
|
+
|
|
44
50
|
@property
|
|
45
|
-
def err(self) -> str:
|
|
51
|
+
def err(self) -> str:
|
|
52
|
+
return self.output.stderr
|
|
53
|
+
|
|
46
54
|
@property
|
|
47
|
-
def returncode(self) -> int:
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
def returncode(self) -> int:
|
|
56
|
+
return self.output.returncode
|
|
57
|
+
|
|
58
|
+
def op2path(self, strict_returncode: bool = True, strict_err: bool = False) -> Union[PathExtended, None]:
|
|
59
|
+
if self.is_successful(strict_returcode=strict_returncode, strict_err=strict_err):
|
|
60
|
+
return PathExtended(self.op.rstrip())
|
|
50
61
|
return None
|
|
51
|
-
|
|
62
|
+
|
|
63
|
+
def op_if_successfull_or_default(self, strict_returcode: bool = True, strict_err: bool = False) -> Optional[str]:
|
|
64
|
+
return self.op if self.is_successful(strict_returcode=strict_returcode, strict_err=strict_err) else None
|
|
65
|
+
|
|
52
66
|
def is_successful(self, strict_returcode: bool = True, strict_err: bool = False) -> bool:
|
|
53
67
|
return ((self.returncode in {0, None}) if strict_returcode else True) and (self.err == "" if strict_err else True)
|
|
68
|
+
|
|
54
69
|
def capture(self):
|
|
55
70
|
for key in ["stdin", "stdout", "stderr"]:
|
|
56
71
|
val: Optional[BinaryIO] = self.std[key]
|
|
57
72
|
if val is not None and val.readable():
|
|
58
73
|
self.output.__dict__[key] = val.read().decode().rstrip()
|
|
59
74
|
return self
|
|
75
|
+
|
|
60
76
|
def print_if_unsuccessful(self, desc: str = "TERMINAL CMD", strict_err: bool = False, strict_returncode: bool = False, assert_success: bool = False):
|
|
61
77
|
success = self.is_successful(strict_err=strict_err, strict_returcode=strict_returncode)
|
|
62
|
-
if assert_success:
|
|
78
|
+
if assert_success:
|
|
79
|
+
assert success, self.print(capture=False, desc=desc)
|
|
63
80
|
if success:
|
|
64
81
|
print(f"✅ {desc} completed successfully")
|
|
65
82
|
else:
|
|
66
83
|
self.print(capture=False, desc=desc)
|
|
67
84
|
return self
|
|
85
|
+
|
|
68
86
|
def print(self, desc: str = "TERMINAL CMD", capture: bool = True):
|
|
69
|
-
if capture:
|
|
87
|
+
if capture:
|
|
88
|
+
self.capture()
|
|
70
89
|
from rich import console
|
|
90
|
+
|
|
71
91
|
con = console.Console()
|
|
72
92
|
from rich.panel import Panel
|
|
73
93
|
from rich.text import Text # from rich.syntax import Syntax; syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True)
|
|
94
|
+
|
|
74
95
|
tmp1 = Text("📥 Input Command:\n")
|
|
75
96
|
tmp1.stylize("u bold blue")
|
|
76
97
|
tmp2 = Text("\n📤 Terminal Response:\n")
|
|
@@ -80,10 +101,12 @@ class Response:
|
|
|
80
101
|
con.print(Panel(txt, title=f"🖥️ {self.desc}", subtitle=f"📋 {desc}", width=150, style="bold cyan on black"))
|
|
81
102
|
return self
|
|
82
103
|
|
|
104
|
+
|
|
83
105
|
# DEPRECATED: Use subprocess.run directly instead of Terminal class.
|
|
84
106
|
# The Terminal class has been replaced with inline subprocess calls to underlying primitives.
|
|
85
107
|
# This file is kept for reference but should not be used.
|
|
86
108
|
|
|
109
|
+
|
|
87
110
|
class Terminal:
|
|
88
111
|
def __init__(self, stdout: Optional[int] = subprocess.PIPE, stderr: Optional[int] = subprocess.PIPE, stdin: Optional[int] = subprocess.PIPE, elevated: bool = False):
|
|
89
112
|
self.machine: str = platform.system()
|
|
@@ -91,88 +114,110 @@ class Terminal:
|
|
|
91
114
|
self.stdout = stdout
|
|
92
115
|
self.stderr = stderr
|
|
93
116
|
self.stdin = stdin
|
|
117
|
+
|
|
94
118
|
# def set_std_system(self): self.stdout = sys.stdout; self.stderr = sys.stderr; self.stdin = sys.stdin
|
|
95
119
|
def set_std_pipe(self):
|
|
96
120
|
self.stdout = subprocess.PIPE
|
|
97
121
|
self.stderr = subprocess.PIPE
|
|
98
122
|
self.stdin = subprocess.PIPE
|
|
123
|
+
|
|
99
124
|
def set_std_null(self):
|
|
100
125
|
self.stdout, self.stderr, self.stdin = subprocess.DEVNULL, subprocess.DEVNULL, subprocess.DEVNULL # Equivalent to `echo 'foo' &> /dev/null`
|
|
126
|
+
|
|
101
127
|
def run(self, *cmds: str, shell: Optional[SHELLS] = "default", check: bool = False, ip: Optional[str] = None) -> Response: # Runs SYSTEM commands like subprocess.run
|
|
102
128
|
"""Blocking operation. Thus, if you start a shell via this method, it will run in the main and won't stop until you exit manually IF stdin is set to sys.stdin, otherwise it will run and close quickly. Other combinations of stdin, stdout can lead to funny behaviour like no output but accept input or opposite.
|
|
103
129
|
* This method is short for: res = subprocess.run("powershell command", capture_output=True, shell=True, text=True) and unlike os.system(cmd), subprocess.run(cmd) gives much more control over the output and input.
|
|
104
130
|
* `shell=True` loads up the profile of the shell called so more specific commands can be run. Importantly, on Windows, the `start` command becomes availalbe and new windows can be launched.
|
|
105
131
|
* `capture_output` prevents the stdout to redirect to the stdout of the script automatically, instead it will be stored in the Response object returned. # `capture_output=True` same as `stdout=subprocess.PIPE, stderr=subprocess.PIPE`"""
|
|
106
|
-
my_list = list(
|
|
107
|
-
|
|
132
|
+
my_list = list(
|
|
133
|
+
cmds
|
|
134
|
+
) # `subprocess.Popen` (process open) is the most general command. Used here to create asynchronous job. `subprocess.run` is a thin wrapper around Popen that makes it wait until it finishes the task. `suprocess.call` is an archaic command for pre-Python-3.5.
|
|
135
|
+
if self.machine == "Windows" and shell in {"powershell", "pwsh"}:
|
|
136
|
+
my_list = [shell, "-Command"] + my_list # alternatively, one can run "cmd"
|
|
108
137
|
if self.elevated is False or self.is_user_admin():
|
|
109
138
|
resp: subprocess.CompletedProcess[str] = subprocess.run(my_list, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=check, input=ip)
|
|
110
139
|
else:
|
|
111
140
|
resp = __import__("ctypes").windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
|
|
112
141
|
return Response.from_completed_process(resp)
|
|
142
|
+
|
|
113
143
|
def run_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
|
|
114
|
-
if self.machine == "Linux":
|
|
115
|
-
|
|
144
|
+
if self.machine == "Linux":
|
|
145
|
+
script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
|
|
146
|
+
script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
|
|
116
147
|
if shell == "default":
|
|
117
148
|
if self.machine == "Windows":
|
|
118
149
|
start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
|
|
119
150
|
full_command: Union[list[str], str] = [start_cmd, str(script_file)] # shell=True will cause this to be a string anyway (with space separation)
|
|
120
151
|
else:
|
|
121
|
-
start_cmd
|
|
152
|
+
start_cmd = "bash"
|
|
122
153
|
full_command = f"{start_cmd} {script_file}" # full_command = [start_cmd, str(script_file)]
|
|
123
|
-
else:
|
|
154
|
+
else:
|
|
155
|
+
full_command = f"{shell} {script_file}" # full_command = [shell, str(tmp_file)]
|
|
124
156
|
if verbose:
|
|
125
|
-
desc="Script to be executed:"
|
|
126
|
-
if platform.system() == "Windows":
|
|
127
|
-
|
|
128
|
-
elif platform.system() == "
|
|
129
|
-
|
|
157
|
+
desc = "Script to be executed:"
|
|
158
|
+
if platform.system() == "Windows":
|
|
159
|
+
lexer = "powershell"
|
|
160
|
+
elif platform.system() == "Linux":
|
|
161
|
+
lexer = "sh"
|
|
162
|
+
elif platform.system() == "Darwin":
|
|
163
|
+
lexer = "sh" # macOS uses similar shell to Linux
|
|
164
|
+
else:
|
|
165
|
+
raise NotImplementedError(f"Platform {platform.system()} not supported.")
|
|
130
166
|
from rich.console import Console
|
|
131
167
|
from rich.panel import Panel
|
|
132
168
|
from rich.syntax import Syntax
|
|
133
169
|
import rich.progress as pb
|
|
170
|
+
|
|
134
171
|
console = Console()
|
|
135
172
|
console.print(Panel(Syntax(code=script, lexer=lexer), title=f"📄 {desc}"), style="bold red")
|
|
136
173
|
with pb.Progress(transient=True) as progress:
|
|
137
174
|
_task = progress.add_task(f"Running Script @ {script_file}", total=None)
|
|
138
175
|
resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
|
|
139
|
-
else:
|
|
176
|
+
else:
|
|
177
|
+
resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
|
|
140
178
|
return Response.from_completed_process(resp)
|
|
179
|
+
|
|
141
180
|
def run_py(self, script: str, wdir: OPLike = None, interactive: bool = True, ipython: bool = True, shell: Optional[str] = None, terminal: str = "", new_window: bool = True, header: bool = True): # async run, since sync run is meaningless.
|
|
142
181
|
script = (Terminal.get_header(wdir=wdir, toolbox=True) if header else "") + script + ("\nDisplayData.set_pandas_auto_width()\n" if terminal in {"wt", "powershell", "pwsh"} else "")
|
|
143
|
-
py_script =
|
|
182
|
+
py_script = PathExtended.tmpfile(name="tmp_python_script", suffix=".py", folder="tmp_scripts/terminal")
|
|
144
183
|
py_script.write_text(f"""print(r'''{script}''')""" + "\n" + script)
|
|
145
184
|
print(f"""🚀 [ASYNC PYTHON SCRIPT] Script URI:
|
|
146
185
|
{py_script.absolute().as_uri()}""")
|
|
147
186
|
print("Script to be executed asyncronously: ", py_script.absolute().as_uri())
|
|
148
187
|
shell_script = f"""
|
|
149
|
-
{f
|
|
150
|
-
{
|
|
188
|
+
{f"cd {wdir}" if wdir is not None else ""}
|
|
189
|
+
{"ipython" if ipython else "python"} {"-i" if interactive else ""} {py_script}
|
|
151
190
|
"""
|
|
152
|
-
shell_script =
|
|
153
|
-
if shell is None and self.machine == "Windows":
|
|
191
|
+
shell_script = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell").write_text(shell_script)
|
|
192
|
+
if shell is None and self.machine == "Windows":
|
|
193
|
+
shell = "pwsh"
|
|
154
194
|
window = "start" if new_window and self.machine == "Windows" else ""
|
|
155
195
|
os.system(f"{window} {terminal} {shell} {shell_script}")
|
|
196
|
+
|
|
156
197
|
@staticmethod
|
|
157
198
|
def is_user_admin() -> bool: # adopted from: https://stackoverflow.com/questions/19672352/how-to-run-script-with-elevated-privilege-on-windows"""
|
|
158
|
-
if os.name ==
|
|
159
|
-
try:
|
|
199
|
+
if os.name == "nt":
|
|
200
|
+
try:
|
|
201
|
+
return __import__("ctypes").windll.shell32.IsUserAnAdmin()
|
|
160
202
|
except Exception:
|
|
161
203
|
import traceback
|
|
204
|
+
|
|
162
205
|
traceback.print_exc()
|
|
163
206
|
print("Admin check failed, assuming not an admin.")
|
|
164
207
|
return False
|
|
165
208
|
else:
|
|
166
209
|
return os.getuid() == 0 # Check for root on Posix
|
|
167
|
-
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
210
|
+
|
|
211
|
+
# @staticmethod
|
|
212
|
+
# def run_as_admin(file: PLike, params: Any, wait: bool = False):
|
|
213
|
+
# proce_info = install_n_import(library="win32com", package="pywin32", fromlist=["shell.shell.ShellExecuteEx"]).shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=file, lpParameters=params)
|
|
214
|
+
# # TODO update PATH for this to take effect immediately.
|
|
215
|
+
# if wait: time.sleep(1)
|
|
216
|
+
# return proce_info
|
|
173
217
|
|
|
174
218
|
@staticmethod
|
|
175
|
-
def get_header(wdir: OPLike, toolbox: bool):
|
|
219
|
+
def get_header(wdir: OPLike, toolbox: bool):
|
|
220
|
+
return f"""
|
|
176
221
|
# >> Code prepended
|
|
177
222
|
{"from crocodile.toolbox import *" if toolbox else "# No toolbox import."}
|
|
178
223
|
{'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
|
machineconfig/utils/utils.py
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
Utils
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from machineconfig.utils.path_reduced import
|
|
5
|
+
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
6
6
|
import machineconfig
|
|
7
7
|
from machineconfig.utils.options import check_tool_exists, choose_cloud_interactively, choose_multiple_options, choose_one_option, choose_ssh_host, display_options
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
from machineconfig.utils.links import build_links, symlink_copy, symlink_func
|
|
11
|
-
from machineconfig.utils.code import
|
|
11
|
+
from machineconfig.utils.code import write_shell_script_to_default_program_path, print_code, PROGRAM_PATH
|
|
12
12
|
from machineconfig.utils.path import sanitize_path, match_file_name
|
|
13
13
|
|
|
14
14
|
# Split into multiple assignments to fix incompatible tuple sizes
|
|
15
|
-
_ =
|
|
15
|
+
_ = print_code, PROGRAM_PATH, display_options, write_shell_script_to_default_program_path
|
|
16
16
|
_ = build_links
|
|
17
17
|
_ = symlink_copy
|
|
18
18
|
_ = symlink_func
|
|
@@ -47,31 +47,34 @@ DEFAULTS_PATH = PathExtended.home().joinpath("dotfiles/machineconfig/defaults.in
|
|
|
47
47
|
# else: print(f"\n❌ ERROR | API request failed: {response.status_code}\n")
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
50
|
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
|
|
53
51
|
dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
|
|
54
52
|
from git import Repo
|
|
53
|
+
|
|
55
54
|
repo = Repo(path=dotfiles_path)
|
|
56
55
|
last_commit = repo.head.commit
|
|
57
56
|
dtm = last_commit.committed_datetime
|
|
58
57
|
from datetime import datetime # make it tz unaware
|
|
58
|
+
|
|
59
59
|
dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
|
|
60
|
-
res =
|
|
60
|
+
res = dtm > datetime.fromisoformat(commit_dtm)
|
|
61
61
|
if res is False and update is True:
|
|
62
62
|
console = Console()
|
|
63
63
|
console.print(Panel(f"🔄 UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
|
|
64
64
|
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
65
|
+
|
|
65
66
|
main(cloud=None, path=dotfiles_path)
|
|
66
67
|
return res
|
|
67
68
|
|
|
69
|
+
|
|
68
70
|
def wait_for_jobs_to_finish(root: PathExtended, pattern: str, wait_for_n_jobs: int, max_wait_minutes: float) -> bool:
|
|
69
|
-
wait_finished: bool=False
|
|
71
|
+
wait_finished: bool = False
|
|
70
72
|
import time
|
|
73
|
+
|
|
71
74
|
t0 = time.time()
|
|
72
75
|
while not wait_finished:
|
|
73
76
|
parts = root.search(pattern, folders=False, r=False)
|
|
74
|
-
counter
|
|
77
|
+
counter = len(parts)
|
|
75
78
|
if counter == wait_for_n_jobs:
|
|
76
79
|
wait_finished = True
|
|
77
80
|
console = Console()
|
|
@@ -88,8 +91,7 @@ def wait_for_jobs_to_finish(root: PathExtended, pattern: str, wait_for_n_jobs: i
|
|
|
88
91
|
return False
|
|
89
92
|
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
if __name__ == '__main__':
|
|
94
|
+
if __name__ == "__main__":
|
|
93
95
|
# import typer
|
|
94
96
|
# typer.run(check_tool_exists)
|
|
95
97
|
pass
|
machineconfig/utils/utils2.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
from typing import Optional, Any
|
|
5
3
|
# import time
|
|
@@ -9,77 +7,69 @@ from typing import Optional, Any
|
|
|
9
7
|
def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bool = True, punctuation: bool = False, safe: bool = False, noun: bool = False) -> str:
|
|
10
8
|
if safe:
|
|
11
9
|
import secrets
|
|
10
|
+
|
|
12
11
|
return secrets.token_urlsafe(length) # interannly, it uses: random.SystemRandom or os.urandom which is hardware-based, not pseudo
|
|
13
12
|
if noun:
|
|
14
13
|
import randomname
|
|
14
|
+
|
|
15
15
|
return randomname.get_name()
|
|
16
16
|
import string
|
|
17
17
|
import random
|
|
18
|
+
|
|
18
19
|
population = (string.ascii_lowercase if lower else "") + (string.ascii_uppercase if upper else "") + (string.digits if digits else "") + (string.punctuation if punctuation else "")
|
|
19
|
-
return
|
|
20
|
+
return "".join(random.choices(population, k=length))
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def read_ini(path:
|
|
23
|
-
if not Path(path).exists() or Path(path).is_dir():
|
|
23
|
+
def read_ini(path: "Path", encoding: Optional[str] = None):
|
|
24
|
+
if not Path(path).exists() or Path(path).is_dir():
|
|
25
|
+
raise FileNotFoundError(f"File not found or is a directory: {path}")
|
|
24
26
|
import configparser
|
|
27
|
+
|
|
25
28
|
res = configparser.ConfigParser()
|
|
26
29
|
res.read(filenames=[str(path)], encoding=encoding)
|
|
27
30
|
return res
|
|
28
|
-
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
|
|
29
34
|
import json
|
|
35
|
+
|
|
30
36
|
try:
|
|
31
|
-
mydict = json.loads(Path(path).read_text(encoding=
|
|
37
|
+
mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
|
|
32
38
|
except Exception:
|
|
33
39
|
import pyjson5
|
|
34
|
-
|
|
40
|
+
|
|
41
|
+
mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
|
|
35
42
|
_ = r
|
|
36
43
|
return mydict
|
|
37
|
-
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_toml(path: "Path"):
|
|
38
47
|
import tomli
|
|
39
|
-
|
|
48
|
+
|
|
49
|
+
return tomli.loads(path.read_text(encoding="utf-8"))
|
|
50
|
+
|
|
40
51
|
|
|
41
52
|
def pprint(obj: dict[Any, Any], title: str) -> None:
|
|
42
53
|
from rich import inspect
|
|
43
54
|
inspect(type("TempStruct", (object,), obj)(), value=False, title=title, docs=False, dunder=False, sort=False)
|
|
55
|
+
|
|
56
|
+
|
|
44
57
|
def get_repr(obj: dict[Any, Any], sep: str = "\n", justify: int = 15, quotes: bool = False):
|
|
45
58
|
return sep.join([f"{key:>{justify}} = {repr(val) if quotes else val}" for key, val in obj.items()])
|
|
46
59
|
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# if self.timeout is not None:
|
|
64
|
-
# import warpt_time_decorator
|
|
65
|
-
# func = wrapt_timeout_decorator.timeout(self.timeout)(func)
|
|
66
|
-
# @wraps(wrapped=func)
|
|
67
|
-
# def wrapper(*args: PS.args, **kwargs: PS.kwargs):
|
|
68
|
-
# t0 = time.time()
|
|
69
|
-
# for idx in range(self.retry):
|
|
70
|
-
# try:
|
|
71
|
-
# return func(*args, **kwargs)
|
|
72
|
-
# except Exception as ex:
|
|
73
|
-
# match self.scaling:
|
|
74
|
-
# case "linear":
|
|
75
|
-
# sleep_time = self.sleep * (idx + 1)
|
|
76
|
-
# case "exponential":
|
|
77
|
-
# sleep_time = self.sleep * (idx + 1)**2
|
|
78
|
-
# print(f"""💥 [RETRY] Function {func.__name__} call failed with error:
|
|
79
|
-
# {ex}
|
|
80
|
-
# Retry count: {idx}/{self.retry}. Sleeping for {sleep_time} seconds.
|
|
81
|
-
# Total elapsed time: {time.time() - t0:0.1f} seconds.""")
|
|
82
|
-
# print(f"""💥 Robust call of `{func}` failed with ```{ex}```.\nretrying {idx}/{self.retry} more times after sleeping for {sleep_time} seconds.\nTotal wait time so far {time.time() - t0: 0.1f} seconds.""")
|
|
83
|
-
# time.sleep(sleep_time)
|
|
84
|
-
# raise RuntimeError(f"💥 Robust call failed after {self.retry} retries and total wait time of {time.time() - t0: 0.1f} seconds.\n{func=}\n{args=}\n{kwargs=}")
|
|
85
|
-
# return wrapper
|
|
61
|
+
def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
|
|
62
|
+
from datetime import datetime
|
|
63
|
+
|
|
64
|
+
result = {}
|
|
65
|
+
for k, v in d.items():
|
|
66
|
+
if isinstance(v, float):
|
|
67
|
+
result[k] = f"{v:.2f}"
|
|
68
|
+
elif isinstance(v, bool):
|
|
69
|
+
result[k] = "✓" if v else "✗"
|
|
70
|
+
elif isinstance(v, int) and len(str(v)) == 13 and v > 0: # assuming ms timestamp
|
|
71
|
+
dt = datetime.fromtimestamp(v / 1000)
|
|
72
|
+
result[k] = dt.strftime("%Y-%m-%d %H:%M")
|
|
73
|
+
else:
|
|
74
|
+
result[k] = v
|
|
75
|
+
return result
|