machineconfig 1.8__py3-none-any.whl → 1.91__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 +4 -2
- machineconfig/jobs/python/check_installations.py +8 -6
- machineconfig/jobs/python/checkout_version.py +27 -32
- machineconfig/jobs/python/create_bootable_media.py +1 -1
- machineconfig/jobs/python/python_cargo_build_share.py +2 -2
- machineconfig/jobs/python/tasks.py +2 -2
- machineconfig/jobs/python_custom_installers/gh.py +53 -0
- machineconfig/jobs/python_custom_installers/hx.py +55 -0
- machineconfig/profile/create.py +26 -21
- machineconfig/profile/create_hardlinks.py +101 -0
- machineconfig/profile/shell.py +5 -5
- machineconfig/scripts/python/choose_wezterm_theme.py +96 -0
- machineconfig/scripts/python/cloud_copy.py +24 -17
- machineconfig/scripts/python/cloud_mount.py +20 -10
- machineconfig/scripts/python/cloud_repo_sync.py +109 -56
- machineconfig/scripts/python/cloud_sync.py +73 -68
- machineconfig/scripts/python/croshell.py +23 -14
- machineconfig/scripts/python/devops.py +19 -20
- machineconfig/scripts/python/devops_backup_retrieve.py +19 -10
- machineconfig/scripts/python/devops_devapps_install.py +81 -57
- machineconfig/scripts/python/devops_update_repos.py +5 -5
- machineconfig/scripts/python/dotfile.py +4 -4
- machineconfig/scripts/python/fire_jobs.py +139 -66
- machineconfig/scripts/python/ftpx.py +17 -7
- machineconfig/scripts/python/gh_models.py +53 -0
- machineconfig/scripts/python/mount_nfs.py +1 -1
- machineconfig/scripts/python/mount_nw_drive.py +3 -3
- machineconfig/scripts/python/mount_ssh.py +2 -3
- machineconfig/scripts/python/pomodoro.py +1 -1
- machineconfig/scripts/python/repos.py +26 -23
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +10 -4
- machineconfig/scripts/python/start_terminals.py +6 -5
- machineconfig/scripts/python/wifi_conn.py +34 -42
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +1 -1
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -2
- machineconfig/utils/installer.py +167 -60
- machineconfig/utils/procs.py +2 -2
- machineconfig/utils/scheduling.py +3 -3
- machineconfig/utils/utils.py +137 -56
- machineconfig/utils/ve.py +171 -100
- machineconfig-1.91.dist-info/LICENSE +201 -0
- {machineconfig-1.8.dist-info → machineconfig-1.91.dist-info}/METADATA +31 -11
- machineconfig-1.91.dist-info/RECORD +69 -0
- {machineconfig-1.8.dist-info → machineconfig-1.91.dist-info}/WHEEL +1 -1
- machineconfig/jobs/script_installer/azure_data_studio.py +0 -22
- machineconfig/jobs/script_installer/bypass_paywall.py +0 -23
- machineconfig/jobs/script_installer/code.py +0 -34
- machineconfig/jobs/script_installer/docker_desktop.py +0 -41
- machineconfig/jobs/script_installer/ngrok.py +0 -29
- machineconfig/jobs/script_installer/skim.py +0 -21
- machineconfig/jobs/script_installer/wezterm.py +0 -34
- machineconfig-1.8.dist-info/RECORD +0 -70
- /machineconfig/jobs/{script_installer → python_custom_installers}/__init__.py +0 -0
- {machineconfig-1.8.dist-info → machineconfig-1.91.dist-info}/top_level.txt +0 -0
machineconfig/utils/utils.py
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
Utils
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from crocodile.
|
|
6
|
+
from crocodile.core import List as L
|
|
7
|
+
from crocodile.file_management import P, randstr, PLike
|
|
7
8
|
from crocodile.meta import Terminal
|
|
8
|
-
from crocodile.core import install_n_import
|
|
9
9
|
# import crocodile.environment as env
|
|
10
10
|
import machineconfig
|
|
11
11
|
from rich.text import Text
|
|
@@ -17,7 +17,7 @@ import subprocess
|
|
|
17
17
|
from typing import Optional, Union, TypeVar, Iterable
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
LIBRARY_ROOT = P(machineconfig.__file__).resolve().parent # .replace(P.home().
|
|
20
|
+
LIBRARY_ROOT = P(machineconfig.__file__).resolve().parent # .replace(P.home().to_str().lower(), P.home().str)
|
|
21
21
|
REPO_ROOT = LIBRARY_ROOT.parent.parent
|
|
22
22
|
PROGRAM_PATH = (P.tmp().joinpath("shells/python_return_command") + (".ps1" if platform.system() == "Windows" else ".sh")).create(parents_only=True)
|
|
23
23
|
CONFIG_PATH = P.home().joinpath(".config/machineconfig")
|
|
@@ -31,8 +31,7 @@ T = TypeVar("T")
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def choose_cloud_interactively() -> str:
|
|
34
|
-
|
|
35
|
-
print(f"Listing Remotes ... ")
|
|
34
|
+
print("Listing Remotes ... ")
|
|
36
35
|
tmp = Terminal().run("rclone listremotes").op_if_successfull_or_default(strict_returcode=False)
|
|
37
36
|
# consider this: remotes = Read.ini(P.home().joinpath(".config/rclone/rclone.conf")).sections()
|
|
38
37
|
if isinstance(tmp, str):
|
|
@@ -40,8 +39,8 @@ def choose_cloud_interactively() -> str:
|
|
|
40
39
|
|
|
41
40
|
else: raise ValueError(f"Got {tmp} from rclone listremotes")
|
|
42
41
|
if len(remotes) == 0:
|
|
43
|
-
raise RuntimeError(
|
|
44
|
-
cloud: str
|
|
42
|
+
raise RuntimeError("You don't have remotes. Configure your rclone first to get cloud services access.")
|
|
43
|
+
cloud: str=choose_one_option(msg="WHICH CLOUD?", options=list(remotes), default=remotes[0], fzf=True)
|
|
45
44
|
return cloud
|
|
46
45
|
|
|
47
46
|
|
|
@@ -49,7 +48,7 @@ def sanitize_path(a_path: P) -> P:
|
|
|
49
48
|
path = P(a_path)
|
|
50
49
|
if path.as_posix().startswith("/home"):
|
|
51
50
|
if platform.system() == "Windows": # path copied from Linux to Windows
|
|
52
|
-
path = P.home().joinpath(*path.parts[
|
|
51
|
+
path = P.home().joinpath(*path.parts[3:]) # exlcude /home/username
|
|
53
52
|
assert path.exists(), f"File not found: {path}"
|
|
54
53
|
print(f"\n{'--' * 50}\n🔗 Mapped `{a_path}` ➡️ `{path}`\n{'--' * 50}\n")
|
|
55
54
|
elif platform.system() == "Linux" and P.home().as_posix() not in path.as_posix(): # copied from Linux to Linux with different username
|
|
@@ -71,63 +70,78 @@ def sanitize_path(a_path: P) -> P:
|
|
|
71
70
|
|
|
72
71
|
def match_file_name(sub_string: str, search_root: Optional[P] = None) -> P:
|
|
73
72
|
"""Look up current directory for file name that matches the passed substring."""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
search_root_obj = search_root if search_root is not None else P.cwd()
|
|
74
|
+
search_root_obj = search_root_obj.absolute()
|
|
75
|
+
print(f"Searching for {sub_string} in {search_root_obj}")
|
|
76
|
+
|
|
77
|
+
search_root_objects = search_root_obj.search("*", not_in=["links", ".venv", ".git", ".idea", ".vscode", "node_modules", "__pycache__"])
|
|
78
|
+
search_results: L[P] = L([a_search_root_obj.search(f"*{sub_string}*", r=True) for a_search_root_obj in search_root_objects]).reduce(lambda x, y: x + y) # type: ignore
|
|
79
|
+
search_results = search_results.filter(lambda x: x.suffix in (".py", ".sh", ".ps1"))
|
|
80
|
+
|
|
77
81
|
if len(search_results) == 1:
|
|
78
82
|
path_obj = search_results.list[0]
|
|
79
83
|
elif len(search_results) > 1:
|
|
80
|
-
|
|
84
|
+
msg = "Search results are ambiguous or non-existent, choose manually:"
|
|
85
|
+
print(msg)
|
|
86
|
+
choice = choose_one_option(msg=msg, options=search_results.list, fzf=True)
|
|
81
87
|
path_obj = P(choice)
|
|
82
88
|
else:
|
|
83
89
|
# let's do a final retry with sub_string.small()
|
|
84
90
|
sub_string_small = sub_string.lower()
|
|
85
91
|
if sub_string_small != sub_string:
|
|
92
|
+
print("Retrying with small letters")
|
|
86
93
|
return match_file_name(sub_string=sub_string_small)
|
|
87
94
|
from git.repo import Repo
|
|
88
95
|
from git.exc import InvalidGitRepositoryError
|
|
89
96
|
try:
|
|
90
|
-
repo = Repo(
|
|
97
|
+
repo = Repo(search_root_obj, search_parent_directories=True)
|
|
91
98
|
repo_root_dir = P(repo.working_dir)
|
|
92
|
-
if repo_root_dir !=
|
|
99
|
+
if repo_root_dir != search_root_obj: # may be user is in a subdirectory of the repo root, try with root dir.
|
|
100
|
+
print("Retrying with root repo instea of cwd")
|
|
93
101
|
return match_file_name(sub_string=sub_string, search_root=repo_root_dir)
|
|
94
102
|
else:
|
|
95
|
-
|
|
103
|
+
search_root_obj = repo_root_dir
|
|
96
104
|
except InvalidGitRepositoryError:
|
|
97
105
|
pass
|
|
98
106
|
|
|
99
|
-
if check_tool_exists("fzf"):
|
|
107
|
+
if check_tool_exists(tool_name="fzf"):
|
|
100
108
|
try:
|
|
101
|
-
|
|
109
|
+
print("Trying with fd ...")
|
|
110
|
+
fzf_cmd = f"cd '{search_root_obj}'; fd --type f --strip-cwd-prefix | fzf --filter={sub_string}"
|
|
111
|
+
search_res = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout.split("\n")[:-1]
|
|
102
112
|
except subprocess.CalledProcessError as cpe:
|
|
103
|
-
print(f"Failed at fzf search with {sub_string} in {
|
|
113
|
+
print(f"Failed at fzf search with {sub_string} in {search_root_obj}.\n{cpe}")
|
|
104
114
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
105
115
|
raise FileNotFoundError(msg) from cpe
|
|
106
|
-
|
|
116
|
+
|
|
117
|
+
if len(search_res) == 1: return search_root_obj.joinpath(search_res[0])
|
|
107
118
|
else:
|
|
119
|
+
print("Tring with raw fzf search ...")
|
|
108
120
|
try:
|
|
109
|
-
res = subprocess.run(f"cd '{
|
|
121
|
+
# res = subprocess.run(f"cd '{search_root_obj}'; fzf --query={sub_string}", check=True, stdout=subprocess.PIPE, text=True, shell=True).stdout.strip()
|
|
122
|
+
res = subprocess.run(f"cd '{search_root_obj}'; fd | fzf --query={sub_string}", check=True, stdout=subprocess.PIPE, text=True, shell=True).stdout.strip()
|
|
110
123
|
except subprocess.CalledProcessError as cpe:
|
|
111
|
-
print(f"Failed at fzf search with {sub_string} in {
|
|
124
|
+
print(f"Failed at fzf search with {sub_string} in {search_root_obj}. {cpe}")
|
|
112
125
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
113
126
|
raise FileNotFoundError(msg) from cpe
|
|
114
|
-
return
|
|
127
|
+
return search_root_obj.joinpath(res)
|
|
128
|
+
|
|
115
129
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
116
130
|
raise FileNotFoundError(msg)
|
|
117
131
|
print(f"\n{'--' * 50}\n🔗 Matched `{sub_string}` ➡️ `{path_obj}`\n{'--' * 50}\n")
|
|
118
132
|
return path_obj
|
|
119
133
|
|
|
120
134
|
|
|
121
|
-
def choose_one_option(options: Iterable[T], header: str
|
|
122
|
-
default: Optional[T] = None, fzf: bool
|
|
135
|
+
def choose_one_option(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
|
|
136
|
+
default: Optional[T] = None, fzf: bool=False, custom_input: bool=False) -> T:
|
|
123
137
|
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
124
138
|
default=default, fzf=fzf, multi=False, custom_input=custom_input)
|
|
125
139
|
assert not isinstance(choice_key, list)
|
|
126
140
|
return choice_key
|
|
127
141
|
|
|
128
142
|
|
|
129
|
-
def choose_multiple_options(options: Iterable[T], header: str
|
|
130
|
-
default: Optional[T] = None, custom_input: bool
|
|
143
|
+
def choose_multiple_options(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
|
|
144
|
+
default: Optional[T] = None, custom_input: bool=False) -> list[T]:
|
|
131
145
|
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
132
146
|
default=default, fzf=True, multi=True,
|
|
133
147
|
custom_input=custom_input)
|
|
@@ -135,14 +149,13 @@ def choose_multiple_options(options: Iterable[T], header: str = "", tail: str =
|
|
|
135
149
|
return [choice_key]
|
|
136
150
|
|
|
137
151
|
|
|
138
|
-
def display_options(msg: str, options: Iterable[T], header: str
|
|
139
|
-
default: Optional[T] = None, fzf: bool
|
|
152
|
+
def display_options(msg: str, options: Iterable[T], header: str="", tail: str="", prompt: str="",
|
|
153
|
+
default: Optional[T] = None, fzf: bool=False, multi: bool=False, custom_input: bool=False) -> Union[T, list[T]]:
|
|
140
154
|
# TODO: replace with https://github.com/tmbo/questionary # also see https://github.com/charmbracelet/gum
|
|
141
155
|
tool_name = "fzf"
|
|
142
156
|
options_strings: list[str] = [str(x) for x in options]
|
|
143
157
|
default_string = str(default) if default is not None else None
|
|
144
158
|
if fzf and check_tool_exists(tool_name):
|
|
145
|
-
install_n_import("pyfzf")
|
|
146
159
|
from pyfzf.pyfzf import FzfPrompt
|
|
147
160
|
fzf_prompt = FzfPrompt()
|
|
148
161
|
nl = "\n"
|
|
@@ -163,7 +176,7 @@ def display_options(msg: str, options: Iterable[T], header: str = "", tail: str
|
|
|
163
176
|
console = Console()
|
|
164
177
|
if default is not None:
|
|
165
178
|
assert default in options, f"Default `{default}` option not in options `{list(options)}`"
|
|
166
|
-
default_msg = Text(
|
|
179
|
+
default_msg = Text(" <<<<-------- DEFAULT", style="bold red")
|
|
167
180
|
else: default_msg = Text("")
|
|
168
181
|
txt = Text("\n" + msg + "\n")
|
|
169
182
|
for idx, key in enumerate(options):
|
|
@@ -176,10 +189,10 @@ def display_options(msg: str, options: Iterable[T], header: str = "", tail: str
|
|
|
176
189
|
|
|
177
190
|
if choice_string == "":
|
|
178
191
|
if default_string is None:
|
|
179
|
-
print(
|
|
192
|
+
print("Default option not available!")
|
|
180
193
|
return display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=multi, custom_input=custom_input)
|
|
181
194
|
choice_idx = options_strings.index(default_string)
|
|
182
|
-
assert default is not None,
|
|
195
|
+
assert default is not None, "🧨 Default option not available!"
|
|
183
196
|
choice_one: T = default
|
|
184
197
|
else:
|
|
185
198
|
try:
|
|
@@ -195,14 +208,15 @@ def display_options(msg: str, options: Iterable[T], header: str = "", tail: str
|
|
|
195
208
|
if choice_string in options_strings: # string input
|
|
196
209
|
choice_idx = options_strings.index(choice_one) # type: ignore #TODO: fix this
|
|
197
210
|
choice_one = list(options)[choice_idx]
|
|
198
|
-
elif custom_input:
|
|
211
|
+
elif custom_input:
|
|
212
|
+
return choice_string # type: ignore #TODO: fix this
|
|
199
213
|
else: raise ValueError(f"Unknown choice. {choice_string}") from te
|
|
200
|
-
print(f"{choice_idx}: {choice_one}",
|
|
214
|
+
print(f"{choice_idx}: {choice_one}", "<<<<-------- CHOICE MADE")
|
|
201
215
|
if multi: return [choice_one]
|
|
202
216
|
return choice_one
|
|
203
217
|
|
|
204
218
|
|
|
205
|
-
def
|
|
219
|
+
def symlink_func(this: P, to_this: P, prioritize_to_this: bool=True):
|
|
206
220
|
"""helper function. creates a symlink from `this` to `to_this`.
|
|
207
221
|
What can go wrong?
|
|
208
222
|
depending on this and to_this existence, one will be prioretized depending on overwrite value.
|
|
@@ -221,13 +235,32 @@ def symlink(this: P, to_this: P, prioritize_to_this: bool = True):
|
|
|
221
235
|
else: this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
|
|
222
236
|
else: # this doesn't exist.
|
|
223
237
|
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
238
|
try:
|
|
226
|
-
|
|
239
|
+
# print(f"Linking {this} ➡️ {to_this}")
|
|
240
|
+
P(this).symlink_to(target=to_this, verbose=True, overwrite=True)
|
|
241
|
+
except Exception as ex: print(f"Failed at linking {this} ➡️ {to_this}.\nReason: {ex}")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def symlink_copy(this: P, to_this: P, prioritize_to_this: bool=True):
|
|
245
|
+
this = P(this).expanduser().absolute()
|
|
246
|
+
to_this = P(to_this).expanduser().absolute()
|
|
247
|
+
if this.is_symlink(): this.delete(sure=True) # delete if it exists as symblic link, not a concrete path.
|
|
248
|
+
if this.exists(): # this is a problem. It will be resolved via `overwrite`
|
|
249
|
+
if prioritize_to_this is True: # it *can* be deleted, but let's look at target first.
|
|
250
|
+
if to_this.exists(): # this exists, to_this as well. to_this is prioritized.
|
|
251
|
+
this.append(f".orig_{randstr()}", inplace=True) # rename is better than deletion
|
|
252
|
+
else: this.move(path=to_this) # this exists, to_this doesn't. to_this is prioritized.
|
|
253
|
+
elif prioritize_to_this is False: # don't sacrefice this, sacrefice to_this.
|
|
254
|
+
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
|
|
255
|
+
else: this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
|
|
256
|
+
else: # this doesn't exist.
|
|
257
|
+
if not to_this.exists(): to_this.touch() # we have to touch it (file) or create it (folder)
|
|
258
|
+
try:
|
|
259
|
+
to_this.copy(path=this, overwrite=True, verbose=True)
|
|
227
260
|
except Exception as ex: print(f"Failed at linking {this} ➡️ {to_this}.\nReason: {ex}")
|
|
228
261
|
|
|
229
262
|
|
|
230
|
-
def get_shell_script_executing_python_file(python_file: str, func: Optional[str] = None, ve_name: str
|
|
263
|
+
def get_shell_script_executing_python_file(python_file: str, func: Optional[str] = None, ve_name: str="ve", strict_execution: bool=True):
|
|
231
264
|
if func is None: exec_line = f"""python {python_file}"""
|
|
232
265
|
else: exec_line = f"""python -m fire {python_file} {func}"""
|
|
233
266
|
shell_script = f"""
|
|
@@ -245,7 +278,15 @@ deactivate || true
|
|
|
245
278
|
return shell_script
|
|
246
279
|
|
|
247
280
|
|
|
248
|
-
def
|
|
281
|
+
def get_shell_script(shell_script: str):
|
|
282
|
+
if platform.system() == "Linux": suffix = ".sh"
|
|
283
|
+
elif platform.system() == "Windows": suffix = ".ps1"
|
|
284
|
+
else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
285
|
+
shell_file = P.tmp().joinpath("tmp_scripts", "shell", randstr() + suffix).create(parents_only=True).write_text(shell_script)
|
|
286
|
+
return shell_file
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_shell_file_executing_python_script(python_script: str, ve_name: str, verbose: bool=True):
|
|
249
290
|
if verbose:
|
|
250
291
|
python_script = f"""
|
|
251
292
|
code = r'''{python_script}'''
|
|
@@ -255,15 +296,12 @@ try:
|
|
|
255
296
|
except ImportError: print(code)
|
|
256
297
|
""" + python_script
|
|
257
298
|
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.
|
|
259
|
-
|
|
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)
|
|
299
|
+
shell_script = get_shell_script_executing_python_file(python_file=python_file.to_str(), ve_name=ve_name)
|
|
300
|
+
shell_file = get_shell_script(shell_script)
|
|
263
301
|
return shell_file
|
|
264
302
|
|
|
265
303
|
|
|
266
|
-
def write_shell_script(program: str, desc: str
|
|
304
|
+
def write_shell_script(program: str, desc: str="", preserve_cwd: bool=True, display: bool=True, execute: bool=False):
|
|
267
305
|
if preserve_cwd:
|
|
268
306
|
if platform.system() == "Windows":
|
|
269
307
|
program = "$orig_path = $pwd\n" + program + "\ncd $orig_path"
|
|
@@ -272,13 +310,13 @@ def write_shell_script(program: str, desc: str = "", preserve_cwd: bool = True,
|
|
|
272
310
|
if display:
|
|
273
311
|
print(f"Executing {PROGRAM_PATH}")
|
|
274
312
|
print_code(code=program, lexer="shell", desc=desc)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
313
|
+
PROGRAM_PATH.create(parents_only=True).write_text(program)
|
|
314
|
+
if execute:
|
|
315
|
+
Terminal().run(f". {PROGRAM_PATH}", shell="powershell").print_if_unsuccessful(desc="Executing shell script", strict_err=True, strict_returncode=True)
|
|
278
316
|
return None
|
|
279
317
|
|
|
280
318
|
|
|
281
|
-
def print_code(code: str, lexer: str, desc: str
|
|
319
|
+
def print_code(code: str, lexer: str, desc: str=""):
|
|
282
320
|
if lexer == "shell":
|
|
283
321
|
if platform.system() == "Windows": lexer = "powershell"
|
|
284
322
|
elif platform.system() == "Linux": lexer = "sh"
|
|
@@ -311,8 +349,8 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
311
349
|
else: raise NotImplementedError(f"platform {platform.system()} not implemented")
|
|
312
350
|
|
|
313
351
|
try:
|
|
314
|
-
_tmp = subprocess.check_output([cmd, tool_name])
|
|
315
|
-
res: bool
|
|
352
|
+
_tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
|
|
353
|
+
res: bool=True
|
|
316
354
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
317
355
|
res = False
|
|
318
356
|
if res is False and install_script is not None:
|
|
@@ -325,13 +363,12 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
325
363
|
def get_ssh_hosts() -> list[str]:
|
|
326
364
|
from paramiko import SSHConfig
|
|
327
365
|
c = SSHConfig()
|
|
328
|
-
c.parse(open(P.home().joinpath(".ssh/config").
|
|
366
|
+
c.parse(open(P.home().joinpath(".ssh/config").to_str(), encoding="utf-8"))
|
|
329
367
|
return list(c.get_hostnames())
|
|
330
|
-
def choose_ssh_host(multi: bool
|
|
331
|
-
|
|
368
|
+
def choose_ssh_host(multi: bool=True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
|
|
332
369
|
|
|
333
370
|
|
|
334
|
-
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool
|
|
371
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool=False):
|
|
335
372
|
dotfiles_path = str(P.home().joinpath("dotfiles"))
|
|
336
373
|
from git import Repo
|
|
337
374
|
repo = Repo(path=dotfiles_path)
|
|
@@ -343,10 +380,54 @@ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool = False):
|
|
|
343
380
|
if res is False and update is True:
|
|
344
381
|
print(f"Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}")
|
|
345
382
|
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
346
|
-
main(cloud=None, path=dotfiles_path
|
|
383
|
+
main(cloud=None, path=dotfiles_path)
|
|
347
384
|
return res
|
|
348
385
|
|
|
349
386
|
|
|
387
|
+
def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
|
|
388
|
+
"""Build symboic links from various relevant paths (e.g. data) to `repo_root/links/<name>` to facilitate easy access from
|
|
389
|
+
tree explorer of the IDE.
|
|
390
|
+
"""
|
|
391
|
+
target_dirs_filtered: list[tuple[P, str]] = []
|
|
392
|
+
for a_dir, a_name in target_paths:
|
|
393
|
+
a_dir_obj = P(a_dir).resolve()
|
|
394
|
+
if not a_dir_obj.exists():
|
|
395
|
+
a_dir_obj.mkdir(parents=True, exist_ok=True)
|
|
396
|
+
target_dirs_filtered.append((a_dir_obj, a_name))
|
|
397
|
+
|
|
398
|
+
import git
|
|
399
|
+
repo = git.Repo(repo_root, search_parent_directories=True)
|
|
400
|
+
root_maybe = repo.working_tree_dir
|
|
401
|
+
assert root_maybe is not None
|
|
402
|
+
repo_root_obj = P(root_maybe)
|
|
403
|
+
tmp_results_root = P.home().joinpath("tmp_results", "tmp_data", repo_root_obj.name)
|
|
404
|
+
tmp_results_root.mkdir(parents=True, exist_ok=True)
|
|
405
|
+
target_dirs_filtered.append((tmp_results_root, "tmp_results"))
|
|
406
|
+
|
|
407
|
+
for a_target_path, a_name in target_dirs_filtered:
|
|
408
|
+
links_path = repo_root_obj.joinpath("links", a_name)
|
|
409
|
+
links_path.symlink_to(target=a_target_path)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def wait_for_jobs_to_finish(root: P, pattern: str, wait_for_n_jobs: int, max_wait_minutes: float) -> bool:
|
|
413
|
+
wait_finished: bool=False
|
|
414
|
+
import time
|
|
415
|
+
t0 = time.time()
|
|
416
|
+
while not wait_finished:
|
|
417
|
+
parts = root.search(pattern, folders=False, r=False)
|
|
418
|
+
counter = len(parts)
|
|
419
|
+
if counter == wait_for_n_jobs:
|
|
420
|
+
wait_finished = True
|
|
421
|
+
print(f"{counter} Jobs finished. Exiting.")
|
|
422
|
+
return True
|
|
423
|
+
if (time.time() - t0) > 60 * max_wait_minutes:
|
|
424
|
+
print(f"Waited for {max_wait_minutes} minutes. Exiting.")
|
|
425
|
+
return False
|
|
426
|
+
print(f"{counter} Jobs finished. Waiting for {wait_for_n_jobs - counter} / {wait_for_n_jobs} jobs to finish, sleeping for 60 seconds.")
|
|
427
|
+
time.sleep(60)
|
|
428
|
+
return False
|
|
429
|
+
|
|
430
|
+
|
|
350
431
|
if __name__ == '__main__':
|
|
351
432
|
# import typer
|
|
352
433
|
# typer.run(check_tool_exists)
|