machineconfig 1.5__py3-none-any.whl → 1.8__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/__init__.py +8 -5
- machineconfig/jobs/python/check_installations.py +173 -163
- machineconfig/jobs/python/checkout_version.py +117 -0
- machineconfig/jobs/python/create_bootable_media.py +14 -14
- machineconfig/jobs/python/create_zellij_template.py +59 -56
- machineconfig/jobs/python/python_cargo_build_share.py +50 -45
- machineconfig/jobs/python/python_ve_symlink.py +20 -18
- machineconfig/jobs/python/tasks.py +4 -0
- machineconfig/jobs/script_installer/azure_data_studio.py +22 -0
- machineconfig/jobs/script_installer/bypass_paywall.py +23 -0
- machineconfig/jobs/script_installer/code.py +34 -0
- machineconfig/jobs/script_installer/docker_desktop.py +41 -0
- machineconfig/jobs/script_installer/ngrok.py +29 -0
- machineconfig/jobs/{python_linux_installers → script_installer}/skim.py +21 -19
- machineconfig/jobs/script_installer/wezterm.py +34 -0
- machineconfig/profile/create.py +107 -200
- machineconfig/profile/shell.py +127 -0
- machineconfig/scripts/__init__.py +6 -6
- machineconfig/scripts/python/cloud_copy.py +93 -0
- machineconfig/scripts/python/cloud_manager.py +38 -0
- machineconfig/scripts/python/cloud_mount.py +115 -52
- machineconfig/scripts/python/cloud_repo_sync.py +154 -114
- machineconfig/scripts/python/cloud_sync.py +261 -79
- machineconfig/scripts/python/croshell.py +151 -0
- machineconfig/scripts/python/devops.py +119 -87
- machineconfig/scripts/python/devops_add_identity.py +27 -23
- machineconfig/scripts/python/devops_add_ssh_key.py +70 -55
- machineconfig/scripts/python/devops_backup_retrieve.py +52 -46
- machineconfig/scripts/python/devops_devapps_install.py +120 -91
- machineconfig/scripts/python/devops_update_repos.py +82 -68
- machineconfig/scripts/python/dotfile.py +42 -38
- machineconfig/scripts/python/fire_jobs.py +351 -98
- machineconfig/scripts/python/ftpx.py +82 -0
- machineconfig/scripts/python/mount_nfs.py +54 -3
- machineconfig/scripts/python/mount_nw_drive.py +31 -0
- machineconfig/scripts/python/mount_ssh.py +44 -20
- machineconfig/scripts/python/onetimeshare.py +60 -51
- machineconfig/scripts/python/pomodoro.py +41 -37
- machineconfig/scripts/python/repos.py +195 -128
- machineconfig/scripts/python/scheduler.py +52 -0
- machineconfig/scripts/python/snapshot.py +21 -21
- machineconfig/scripts/python/start_slidev.py +104 -0
- machineconfig/scripts/python/start_terminals.py +97 -0
- machineconfig/scripts/python/wifi_conn.py +90 -71
- machineconfig/scripts/python/{transfer_wsl_win.py → wsl_windows_transfer.py} +47 -39
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +44 -48
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +136 -130
- machineconfig/utils/installer.py +251 -0
- machineconfig/utils/procs.py +114 -64
- machineconfig/utils/scheduling.py +188 -0
- machineconfig/utils/utils.py +353 -249
- machineconfig/utils/ve.py +222 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/METADATA +140 -110
- machineconfig-1.8.dist-info/RECORD +70 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/WHEEL +1 -1
- machineconfig/jobs/python/python_linux_installers_all.py +0 -73
- machineconfig/jobs/python/python_ve_installer.py +0 -73
- machineconfig/jobs/python/python_windows_installers_all.py +0 -23
- machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -15
- machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -32
- machineconfig/jobs/python_generic_installers/archive/vtm.py +0 -25
- machineconfig/jobs/python_generic_installers/broot.py +0 -39
- machineconfig/jobs/python_generic_installers/browsh.py +0 -25
- machineconfig/jobs/python_generic_installers/bw.py +0 -26
- machineconfig/jobs/python_generic_installers/chatgpt.py +0 -24
- machineconfig/jobs/python_generic_installers/cpufetch.py +0 -23
- machineconfig/jobs/python_generic_installers/delta.py +0 -59
- machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
- machineconfig/jobs/python_generic_installers/dev/autogpt.py +0 -28
- machineconfig/jobs/python_generic_installers/dev/bw.py +0 -29
- machineconfig/jobs/python_generic_installers/dev/evcxr.py +0 -22
- machineconfig/jobs/python_generic_installers/dev/kondo.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/lvim.py +0 -60
- machineconfig/jobs/python_generic_installers/dev/ngrok.py +0 -35
- machineconfig/jobs/python_generic_installers/dev/opencommit.py +0 -21
- machineconfig/jobs/python_generic_installers/dev/qrcp.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/qrscan.py +0 -16
- machineconfig/jobs/python_generic_installers/dev/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/dev/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/dev/tldr.py +0 -25
- machineconfig/jobs/python_generic_installers/dev/tokei.py +0 -24
- machineconfig/jobs/python_generic_installers/diskonaut.py +0 -26
- machineconfig/jobs/python_generic_installers/dua.py +0 -21
- machineconfig/jobs/python_generic_installers/evcxr.py +0 -21
- machineconfig/jobs/python_generic_installers/gitui.py +0 -23
- machineconfig/jobs/python_generic_installers/gopass.py +0 -19
- machineconfig/jobs/python_generic_installers/helix.py +0 -45
- machineconfig/jobs/python_generic_installers/kondo.py +0 -20
- machineconfig/jobs/python_generic_installers/lf.py +0 -25
- machineconfig/jobs/python_generic_installers/lvim.py +0 -34
- machineconfig/jobs/python_generic_installers/mprocs.py +0 -20
- machineconfig/jobs/python_generic_installers/navi.py +0 -20
- machineconfig/jobs/python_generic_installers/ots.py +0 -26
- machineconfig/jobs/python_generic_installers/ouch.py +0 -25
- machineconfig/jobs/python_generic_installers/pomodoro.py +0 -19
- machineconfig/jobs/python_generic_installers/procs.py +0 -20
- machineconfig/jobs/python_generic_installers/qrcp.py +0 -22
- machineconfig/jobs/python_generic_installers/qrscan.py +0 -14
- machineconfig/jobs/python_generic_installers/rclone.py +0 -21
- machineconfig/jobs/python_generic_installers/rust-analyzer.py +0 -24
- machineconfig/jobs/python_generic_installers/tere.py +0 -26
- machineconfig/jobs/python_generic_installers/termscp.py +0 -23
- machineconfig/jobs/python_generic_installers/tldr.py +0 -24
- machineconfig/jobs/python_generic_installers/tokei.py +0 -21
- machineconfig/jobs/python_generic_installers/vtm.py +0 -26
- machineconfig/jobs/python_generic_installers/watchexec.py +0 -21
- machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
- machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -18
- machineconfig/jobs/python_linux_installers/bandwhich.py +0 -11
- machineconfig/jobs/python_linux_installers/bottom.py +0 -17
- machineconfig/jobs/python_linux_installers/btop.py +0 -17
- machineconfig/jobs/python_linux_installers/dev/bandwhich.py +0 -13
- machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
- machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -21
- machineconfig/jobs/python_linux_installers/gotty.py +0 -16
- machineconfig/jobs/python_linux_installers/joshuto.py +0 -28
- machineconfig/jobs/python_linux_installers/mcfly.py +0 -12
- machineconfig/jobs/python_linux_installers/nnn.py +0 -18
- machineconfig/jobs/python_linux_installers/topgrade.py +0 -15
- machineconfig/jobs/python_linux_installers/viu.py +0 -19
- machineconfig/jobs/python_linux_installers/xplr.py +0 -22
- machineconfig/jobs/python_linux_installers/zellij.py +0 -31
- machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -20
- machineconfig/jobs/python_windows_installers/bat.py +0 -16
- machineconfig/jobs/python_windows_installers/boxes.py +0 -19
- machineconfig/jobs/python_windows_installers/bypass_paywall.py +0 -18
- machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -21
- machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -20
- machineconfig/jobs/python_windows_installers/fd.py +0 -17
- machineconfig/jobs/python_windows_installers/fzf.py +0 -19
- machineconfig/jobs/python_windows_installers/obs_background_removal_plugin.py +0 -19
- machineconfig/jobs/python_windows_installers/rg.py +0 -15
- machineconfig/jobs/python_windows_installers/ugrep.py +0 -14
- machineconfig/jobs/python_windows_installers/zoomit.py +0 -20
- machineconfig/jobs/python_windows_installers/zoxide.py +0 -20
- machineconfig/profile/fix_shell_profiles.py +0 -8
- machineconfig/scripts/python/archive/__init__.py +0 -0
- machineconfig/scripts/python/archive/bu_gdrive_rx.py +0 -41
- machineconfig/scripts/python/archive/bu_gdrive_sx.py +0 -40
- machineconfig/scripts/python/archive/bu_onedrive_rx.py +0 -59
- machineconfig/scripts/python/archive/bu_onedrive_sx.py +0 -45
- machineconfig/scripts/python/chatgpt.py +0 -28
- machineconfig/scripts/python/choose_ohmybash_theme.py +0 -25
- machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -40
- machineconfig/scripts/python/cloud_rx.py +0 -42
- machineconfig/scripts/python/cloud_sx.py +0 -40
- machineconfig/scripts/python/ftprx.py +0 -37
- machineconfig/scripts/python/ftpsx.py +0 -36
- machineconfig/scripts/python/im2text.py +0 -15
- machineconfig/scripts/python/tmate_conn.py +0 -28
- machineconfig/scripts/python/tmate_start.py +0 -31
- machineconfig/utils/to_exe.py +0 -7
- machineconfig-1.5.dist-info/RECORD +0 -147
- /machineconfig/jobs/{python_generic_installers/archive → script_installer}/__init__.py +0 -0
- {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/top_level.txt +0 -0
machineconfig/utils/utils.py
CHANGED
|
@@ -1,249 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
else:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Utils
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from crocodile.file_management import P, randstr
|
|
7
|
+
from crocodile.meta import Terminal
|
|
8
|
+
from crocodile.core import install_n_import
|
|
9
|
+
# import crocodile.environment as env
|
|
10
|
+
import machineconfig
|
|
11
|
+
from rich.text import Text
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.syntax import Syntax
|
|
15
|
+
import platform
|
|
16
|
+
import subprocess
|
|
17
|
+
from typing import Optional, Union, TypeVar, Iterable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
LIBRARY_ROOT = P(machineconfig.__file__).resolve().parent # .replace(P.home().str.lower(), P.home().str)
|
|
21
|
+
REPO_ROOT = LIBRARY_ROOT.parent.parent
|
|
22
|
+
PROGRAM_PATH = (P.tmp().joinpath("shells/python_return_command") + (".ps1" if platform.system() == "Windows" else ".sh")).create(parents_only=True)
|
|
23
|
+
CONFIG_PATH = P.home().joinpath(".config/machineconfig")
|
|
24
|
+
INSTALL_VERSION_ROOT = CONFIG_PATH.joinpath("cli_tools_installers/versions")
|
|
25
|
+
INSTALL_TMP_DIR = P.tmp(folder="tmp_installers")
|
|
26
|
+
|
|
27
|
+
DEFAULTS_PATH = P.home().joinpath("dotfiles/machineconfig/defaults.ini")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def choose_cloud_interactively() -> str:
|
|
34
|
+
from crocodile.core import List as L
|
|
35
|
+
print(f"Listing Remotes ... ")
|
|
36
|
+
tmp = Terminal().run("rclone listremotes").op_if_successfull_or_default(strict_returcode=False)
|
|
37
|
+
# consider this: remotes = Read.ini(P.home().joinpath(".config/rclone/rclone.conf")).sections()
|
|
38
|
+
if isinstance(tmp, str):
|
|
39
|
+
remotes: list[str] = L(tmp.splitlines()).apply(lambda x: x.replace(":", "")).list
|
|
40
|
+
|
|
41
|
+
else: raise ValueError(f"Got {tmp} from rclone listremotes")
|
|
42
|
+
if len(remotes) == 0:
|
|
43
|
+
raise RuntimeError(f"You don't have remotes. Configure your rclone first to get cloud services access.")
|
|
44
|
+
cloud: str = choose_one_option(msg="WHICH CLOUD?", options=list(remotes), default=remotes[0], fzf=True)
|
|
45
|
+
return cloud
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def sanitize_path(a_path: P) -> P:
|
|
49
|
+
path = P(a_path)
|
|
50
|
+
if path.as_posix().startswith("/home"):
|
|
51
|
+
if platform.system() == "Windows": # path copied from Linux to Windows
|
|
52
|
+
path = P.home().joinpath(*path.parts[2:]) # exlcude /home/username
|
|
53
|
+
assert path.exists(), f"File not found: {path}"
|
|
54
|
+
print(f"\n{'--' * 50}\n🔗 Mapped `{a_path}` ➡️ `{path}`\n{'--' * 50}\n")
|
|
55
|
+
elif platform.system() == "Linux" and P.home().as_posix() not in path.as_posix(): # copied from Linux to Linux with different username
|
|
56
|
+
path = P.home().joinpath(*path.parts[3:]) # exlcude /home/username (three parts: /, home, username)
|
|
57
|
+
assert path.exists(), f"File not found: {path}"
|
|
58
|
+
print(f"\n{'--' * 50}\n🔗 Mapped `{a_path}` ➡️ `{path}`\n{'--' * 50}\n")
|
|
59
|
+
elif path.as_posix().startswith("C:"):
|
|
60
|
+
if platform.system() == "Linux": # path copied from Windows to Linux
|
|
61
|
+
xx = str(a_path).replace("\\", "/")
|
|
62
|
+
path = P.home().joinpath(*P(xx).parts[3:]) # exlcude C:\Users\username
|
|
63
|
+
assert path.exists(), f"File not found: {path}"
|
|
64
|
+
print(f"\n{'--' * 50}\n🔗 Mapped `{a_path}` ➡️ `{path}`\n{'--' * 50}\n")
|
|
65
|
+
elif platform.system() == "Windows" and P.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
|
|
66
|
+
path = P.home().joinpath(*path.parts[2:])
|
|
67
|
+
assert path.exists(), f"File not found: {path}"
|
|
68
|
+
print(f"\n{'--' * 50}\n🔗 Mapped `{a_path}` ➡️ `{path}`\n{'--' * 50}\n")
|
|
69
|
+
return path.expanduser().absolute()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def match_file_name(sub_string: str, search_root: Optional[P] = None) -> P:
|
|
73
|
+
"""Look up current directory for file name that matches the passed substring."""
|
|
74
|
+
root = search_root if search_root is not None else P.cwd()
|
|
75
|
+
print(f"Searching for {sub_string} in {root}")
|
|
76
|
+
search_results = root.absolute().search(f"*{sub_string}*.py", r=True)
|
|
77
|
+
if len(search_results) == 1:
|
|
78
|
+
path_obj = search_results.list[0]
|
|
79
|
+
elif len(search_results) > 1:
|
|
80
|
+
choice = choose_one_option(msg=f"Search results are ambiguous or non-existent", options=search_results.list, fzf=True)
|
|
81
|
+
path_obj = P(choice)
|
|
82
|
+
else:
|
|
83
|
+
# let's do a final retry with sub_string.small()
|
|
84
|
+
sub_string_small = sub_string.lower()
|
|
85
|
+
if sub_string_small != sub_string:
|
|
86
|
+
return match_file_name(sub_string=sub_string_small)
|
|
87
|
+
from git.repo import Repo
|
|
88
|
+
from git.exc import InvalidGitRepositoryError
|
|
89
|
+
try:
|
|
90
|
+
repo = Repo(root, search_parent_directories=True)
|
|
91
|
+
repo_root_dir = P(repo.working_dir)
|
|
92
|
+
if repo_root_dir != root: # may be user is in a subdirectory of the repo root, try with root dir.
|
|
93
|
+
return match_file_name(sub_string=sub_string, search_root=repo_root_dir)
|
|
94
|
+
else:
|
|
95
|
+
root = repo_root_dir
|
|
96
|
+
except InvalidGitRepositoryError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
if check_tool_exists("fzf"):
|
|
100
|
+
try:
|
|
101
|
+
search_res = subprocess.run(f"cd '{root}'; fzf --filter={sub_string}", stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout.split("\n")[:-1]
|
|
102
|
+
except subprocess.CalledProcessError as cpe:
|
|
103
|
+
print(f"Failed at fzf search with {sub_string} in {root}.\n{cpe}")
|
|
104
|
+
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
105
|
+
raise FileNotFoundError(msg) from cpe
|
|
106
|
+
if len(search_res) == 1: return root.joinpath(search_res[0])
|
|
107
|
+
else:
|
|
108
|
+
try:
|
|
109
|
+
res = subprocess.run(f"cd '{root}'; fzf --query={sub_string}", check=True, stdout=subprocess.PIPE, text=True, shell=True).stdout.strip()
|
|
110
|
+
except subprocess.CalledProcessError as cpe:
|
|
111
|
+
print(f"Failed at fzf search with {sub_string} in {root}. {cpe}")
|
|
112
|
+
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
113
|
+
raise FileNotFoundError(msg) from cpe
|
|
114
|
+
return root.joinpath(res)
|
|
115
|
+
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
116
|
+
raise FileNotFoundError(msg)
|
|
117
|
+
print(f"\n{'--' * 50}\n🔗 Matched `{sub_string}` ➡️ `{path_obj}`\n{'--' * 50}\n")
|
|
118
|
+
return path_obj
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def choose_one_option(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "",
|
|
122
|
+
default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
|
|
123
|
+
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
124
|
+
default=default, fzf=fzf, multi=False, custom_input=custom_input)
|
|
125
|
+
assert not isinstance(choice_key, list)
|
|
126
|
+
return choice_key
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def choose_multiple_options(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "",
|
|
130
|
+
default: Optional[T] = None, custom_input: bool = False) -> list[T]:
|
|
131
|
+
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
132
|
+
default=default, fzf=True, multi=True,
|
|
133
|
+
custom_input=custom_input)
|
|
134
|
+
if isinstance(choice_key, list): return choice_key
|
|
135
|
+
return [choice_key]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def display_options(msg: str, options: Iterable[T], header: str = "", tail: str = "", prompt: str = "",
|
|
139
|
+
default: Optional[T] = None, fzf: bool = False, multi: bool = False, custom_input: bool = False) -> Union[T, list[T]]:
|
|
140
|
+
# TODO: replace with https://github.com/tmbo/questionary # also see https://github.com/charmbracelet/gum
|
|
141
|
+
tool_name = "fzf"
|
|
142
|
+
options_strings: list[str] = [str(x) for x in options]
|
|
143
|
+
default_string = str(default) if default is not None else None
|
|
144
|
+
if fzf and check_tool_exists(tool_name):
|
|
145
|
+
install_n_import("pyfzf")
|
|
146
|
+
from pyfzf.pyfzf import FzfPrompt
|
|
147
|
+
fzf_prompt = FzfPrompt()
|
|
148
|
+
nl = "\n"
|
|
149
|
+
choice_string_multi: list[str] = fzf_prompt.prompt(choices=options_strings, fzf_options=("--multi" if multi else "") + f' --prompt "{prompt.replace(nl, " ")}" ') # --border-label={msg.replace(nl, ' ')}")
|
|
150
|
+
# --border=rounded doens't work on older versions of fzf installed at Ubuntu 20.04
|
|
151
|
+
if not multi:
|
|
152
|
+
try:
|
|
153
|
+
choice_one_string = choice_string_multi[0]
|
|
154
|
+
choice_idx = options_strings.index(choice_one_string)
|
|
155
|
+
return list(options)[choice_idx]
|
|
156
|
+
except IndexError as ie:
|
|
157
|
+
print(f"{options=}, {choice_string_multi=}")
|
|
158
|
+
print(choice_string_multi)
|
|
159
|
+
raise ie
|
|
160
|
+
choice_idx_s = [options_strings.index(x) for x in choice_string_multi]
|
|
161
|
+
return [list(options)[x] for x in choice_idx_s]
|
|
162
|
+
else:
|
|
163
|
+
console = Console()
|
|
164
|
+
if default is not None:
|
|
165
|
+
assert default in options, f"Default `{default}` option not in options `{list(options)}`"
|
|
166
|
+
default_msg = Text(f" <<<<-------- DEFAULT", style="bold red")
|
|
167
|
+
else: default_msg = Text("")
|
|
168
|
+
txt = Text("\n" + msg + "\n")
|
|
169
|
+
for idx, key in enumerate(options):
|
|
170
|
+
txt = txt + Text(f"{idx:2d} ", style="bold blue") + str(key) + (default_msg if default is not None and default == key else "") + "\n"
|
|
171
|
+
txt_panel = Panel(txt, title=header, subtitle=tail, border_style="bold red")
|
|
172
|
+
console.print(txt_panel)
|
|
173
|
+
if default is not None:
|
|
174
|
+
choice_string = input(f"{prompt}\nEnter option number or hit enter for default choice: ")
|
|
175
|
+
else: choice_string = input(f"{prompt}\nEnter option number: ")
|
|
176
|
+
|
|
177
|
+
if choice_string == "":
|
|
178
|
+
if default_string is None:
|
|
179
|
+
print(f"Default option not available!")
|
|
180
|
+
return display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
|
|
181
|
+
choice_idx = options_strings.index(default_string)
|
|
182
|
+
assert default is not None, f"🧨 Default option not available!"
|
|
183
|
+
choice_one: T = default
|
|
184
|
+
else:
|
|
185
|
+
try:
|
|
186
|
+
choice_idx = int(choice_string, base=10)
|
|
187
|
+
choice_one = list(options)[choice_idx]
|
|
188
|
+
except IndexError as ie: # i.e. converting to integer was successful but indexing failed.
|
|
189
|
+
if choice_string in options_strings: # string input
|
|
190
|
+
choice_idx = options_strings.index(choice_one) # type: ignore #TODO: fix this
|
|
191
|
+
choice_one = list(options)[choice_idx]
|
|
192
|
+
elif custom_input: return str(choice_string) # type: ignore #TODO: fix this
|
|
193
|
+
else: raise ValueError(f"Unknown choice. {choice_string}") from ie
|
|
194
|
+
except TypeError as te: # int(choice_string) failed due to # either the number is invalid, or the input is custom.
|
|
195
|
+
if choice_string in options_strings: # string input
|
|
196
|
+
choice_idx = options_strings.index(choice_one) # type: ignore #TODO: fix this
|
|
197
|
+
choice_one = list(options)[choice_idx]
|
|
198
|
+
elif custom_input: return str(choice_string) # type: ignore #TODO: fix this
|
|
199
|
+
else: raise ValueError(f"Unknown choice. {choice_string}") from te
|
|
200
|
+
print(f"{choice_idx}: {choice_one}", f"<<<<-------- CHOICE MADE")
|
|
201
|
+
if multi: return [choice_one]
|
|
202
|
+
return choice_one
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def symlink(this: P, to_this: P, prioritize_to_this: bool = True):
|
|
206
|
+
"""helper function. creates a symlink from `this` to `to_this`.
|
|
207
|
+
What can go wrong?
|
|
208
|
+
depending on this and to_this existence, one will be prioretized depending on overwrite value.
|
|
209
|
+
True means this will potentially be overwritten (depending on whether to_this exists or not)
|
|
210
|
+
False means to_this will potentially be overwittten."""
|
|
211
|
+
this = P(this).expanduser().absolute()
|
|
212
|
+
to_this = P(to_this).expanduser().absolute()
|
|
213
|
+
if this.is_symlink(): this.delete(sure=True) # delete if it exists as symblic link, not a concrete path.
|
|
214
|
+
if this.exists(): # this is a problem. It will be resolved via `overwrite`
|
|
215
|
+
if prioritize_to_this is True: # it *can* be deleted, but let's look at target first.
|
|
216
|
+
if to_this.exists(): # this exists, to_this as well. to_this is prioritized.
|
|
217
|
+
this.append(f".orig_{randstr()}", inplace=True) # rename is better than deletion
|
|
218
|
+
else: this.move(path=to_this) # this exists, to_this doesn't. to_this is prioritized.
|
|
219
|
+
elif prioritize_to_this is False: # don't sacrefice this, sacrefice to_this.
|
|
220
|
+
if to_this.exists(): this.move(path=to_this, overwrite=True) # this exists, to_this as well, this is prioritized. # now we are readly to make the link
|
|
221
|
+
else: this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
|
|
222
|
+
else: # this doesn't exist.
|
|
223
|
+
if not to_this.exists(): to_this.touch() # we have to touch it (file) or create it (folder)
|
|
224
|
+
if platform.system() == "Windows": _ = install_n_import("win32api", "pywin32") # this is crucial for windows to pop up the concent window in case python was not run as admin.
|
|
225
|
+
try:
|
|
226
|
+
P(this).symlink_to(to_this, verbose=True, overwrite=True)
|
|
227
|
+
except Exception as ex: print(f"Failed at linking {this} ➡️ {to_this}.\nReason: {ex}")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_shell_script_executing_python_file(python_file: str, func: Optional[str] = None, ve_name: str = "ve", strict_execution: bool = True):
|
|
231
|
+
if func is None: exec_line = f"""python {python_file}"""
|
|
232
|
+
else: exec_line = f"""python -m fire {python_file} {func}"""
|
|
233
|
+
shell_script = f"""
|
|
234
|
+
. $HOME/scripts/activate_ve {ve_name}
|
|
235
|
+
echo "Executing {exec_line}"
|
|
236
|
+
{exec_line}
|
|
237
|
+
deactivate || true
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
if strict_execution:
|
|
241
|
+
if platform.system() == "Windows": shell_script = """$ErrorActionPreference = "Stop" """ + "\n" + shell_script
|
|
242
|
+
if platform.system() == "Linux": shell_script = "set -e" + "\n" + shell_script
|
|
243
|
+
|
|
244
|
+
if platform.system() == "Linux": shell_script = "#!/bin/bash" + "\n" + shell_script # vs #!/usr/bin/env bash
|
|
245
|
+
return shell_script
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_shell_file_executing_python_script(python_script: str, ve_name: str = "ve", verbose: bool = True):
|
|
249
|
+
if verbose:
|
|
250
|
+
python_script = f"""
|
|
251
|
+
code = r'''{python_script}'''
|
|
252
|
+
try:
|
|
253
|
+
from machineconfig.utils.utils import print_code
|
|
254
|
+
print_code(code=code, lexer="python", desc="Python Script")
|
|
255
|
+
except ImportError: print(code)
|
|
256
|
+
""" + python_script
|
|
257
|
+
python_file = P.tmp().joinpath("tmp_scripts", "python", randstr() + ".py").create(parents_only=True).write_text(python_script)
|
|
258
|
+
shell_script = get_shell_script_executing_python_file(python_file=python_file.str, ve_name=ve_name)
|
|
259
|
+
if platform.system() == "Linux": suffix = ".sh"
|
|
260
|
+
elif platform.system() == "Windows": suffix = ".ps1"
|
|
261
|
+
else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
262
|
+
shell_file = P.tmp().joinpath("tmp_scripts", "shell", randstr() + suffix).create(parents_only=True).write_text(shell_script)
|
|
263
|
+
return shell_file
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def write_shell_script(program: str, desc: str = "", preserve_cwd: bool = True, display: bool = True, execute: bool = False):
|
|
267
|
+
if preserve_cwd:
|
|
268
|
+
if platform.system() == "Windows":
|
|
269
|
+
program = "$orig_path = $pwd\n" + program + "\ncd $orig_path"
|
|
270
|
+
else:
|
|
271
|
+
program = 'orig_path=$(cd -- "." && pwd)\n' + program + '\ncd "$orig_path" || exit'
|
|
272
|
+
if display:
|
|
273
|
+
print(f"Executing {PROGRAM_PATH}")
|
|
274
|
+
print_code(code=program, lexer="shell", desc=desc)
|
|
275
|
+
if platform.system() == 'Windows': PROGRAM_PATH.create(parents_only=True).write_text(program)
|
|
276
|
+
else: PROGRAM_PATH.create(parents_only=True).write_text(f"{program}")
|
|
277
|
+
if execute: Terminal().run(f". {PROGRAM_PATH}", shell="powershell").print_if_unsuccessful(desc="Executing shell script", strict_err=True, strict_returncode=True)
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def print_code(code: str, lexer: str, desc: str = ""):
|
|
282
|
+
if lexer == "shell":
|
|
283
|
+
if platform.system() == "Windows": lexer = "powershell"
|
|
284
|
+
elif platform.system() == "Linux": lexer = "sh"
|
|
285
|
+
else: raise NotImplementedError(f"lexer {lexer} not implemented for system {platform.system()}")
|
|
286
|
+
console = Console()
|
|
287
|
+
console.print(Panel(Syntax(code=code, lexer=lexer), title=desc), style="bold red")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# def get_latest_version(url: str) -> None:
|
|
291
|
+
# # not yet used, consider, using it.
|
|
292
|
+
# import requests
|
|
293
|
+
# import json
|
|
294
|
+
# url = f"https://api.github.com/repos/{url.split('github.com/')[1]}/releases/latest"
|
|
295
|
+
# # Replace {owner} and {repo} with the actual owner and repository name
|
|
296
|
+
# response = requests.get(url, timeout=10)
|
|
297
|
+
# if response.status_code == 200:
|
|
298
|
+
# data = json.loads(response.text)
|
|
299
|
+
# latest_version = data["tag_name"]
|
|
300
|
+
# print("Latest release version:", latest_version)
|
|
301
|
+
# else: print("Error:", response.status_code)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> bool:
|
|
305
|
+
"""This is the CLI equivalent of `install_n_import` function of crocodile. """
|
|
306
|
+
if platform.system() == "Windows":
|
|
307
|
+
tool_name = tool_name.replace(".exe", "") + ".exe"
|
|
308
|
+
|
|
309
|
+
if platform.system() == "Windows": cmd = "where.exe"
|
|
310
|
+
elif platform.system() == "Linux": cmd = "which"
|
|
311
|
+
else: raise NotImplementedError(f"platform {platform.system()} not implemented")
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
_tmp = subprocess.check_output([cmd, tool_name])
|
|
315
|
+
res: bool = True
|
|
316
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
317
|
+
res = False
|
|
318
|
+
if res is False and install_script is not None:
|
|
319
|
+
print(f"Installing {tool_name} ...")
|
|
320
|
+
Terminal().run(install_script, shell="powershell").print()
|
|
321
|
+
return check_tool_exists(tool_name=tool_name, install_script=None)
|
|
322
|
+
return res
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_ssh_hosts() -> list[str]:
|
|
326
|
+
from paramiko import SSHConfig
|
|
327
|
+
c = SSHConfig()
|
|
328
|
+
c.parse(open(P.home().joinpath(".ssh/config").str, encoding="utf-8"))
|
|
329
|
+
return list(c.get_hostnames())
|
|
330
|
+
def choose_ssh_host(multi: bool = True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool = False):
|
|
335
|
+
dotfiles_path = str(P.home().joinpath("dotfiles"))
|
|
336
|
+
from git import Repo
|
|
337
|
+
repo = Repo(path=dotfiles_path)
|
|
338
|
+
last_commit = repo.head.commit
|
|
339
|
+
dtm = last_commit.committed_datetime
|
|
340
|
+
from datetime import datetime # make it tz unaware
|
|
341
|
+
dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
|
|
342
|
+
res = dtm > datetime.fromisoformat(commit_dtm)
|
|
343
|
+
if res is False and update is True:
|
|
344
|
+
print(f"Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}")
|
|
345
|
+
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
346
|
+
main(cloud=None, path=dotfiles_path, push=False)
|
|
347
|
+
return res
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
if __name__ == '__main__':
|
|
351
|
+
# import typer
|
|
352
|
+
# typer.run(check_tool_exists)
|
|
353
|
+
pass
|