machineconfig 1.9__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 +1 -1
- machineconfig/jobs/python/check_installations.py +7 -5
- 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/{helix.py → hx.py} +17 -5
- machineconfig/profile/create.py +23 -21
- machineconfig/profile/create_hardlinks.py +101 -0
- machineconfig/profile/shell.py +5 -5
- machineconfig/scripts/python/cloud_copy.py +19 -16
- machineconfig/scripts/python/cloud_repo_sync.py +92 -47
- machineconfig/scripts/python/cloud_sync.py +70 -63
- machineconfig/scripts/python/croshell.py +6 -6
- machineconfig/scripts/python/devops.py +19 -20
- machineconfig/scripts/python/devops_backup_retrieve.py +19 -10
- machineconfig/scripts/python/devops_devapps_install.py +80 -62
- machineconfig/scripts/python/devops_update_repos.py +5 -5
- machineconfig/scripts/python/dotfile.py +4 -4
- machineconfig/scripts/python/fire_jobs.py +57 -23
- 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 +21 -22
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +10 -4
- machineconfig/scripts/python/start_terminals.py +5 -4
- 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 +78 -39
- machineconfig/utils/procs.py +2 -2
- machineconfig/utils/scheduling.py +3 -3
- machineconfig/utils/utils.py +131 -52
- machineconfig/utils/ve.py +145 -95
- {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/METADATA +160 -155
- machineconfig-1.91.dist-info/RECORD +69 -0
- machineconfig/jobs/python_custom_installers/azuredatastudio.py +0 -36
- machineconfig/jobs/python_custom_installers/bypass_paywall.py +0 -30
- machineconfig/jobs/python_custom_installers/docker_desktop.py +0 -52
- machineconfig/jobs/python_custom_installers/goes.py +0 -35
- machineconfig/jobs/python_custom_installers/lvim.py +0 -48
- machineconfig/jobs/python_custom_installers/ngrok.py +0 -39
- machineconfig/jobs/python_custom_installers/nvim.py +0 -48
- machineconfig/jobs/python_custom_installers/vscode.py +0 -45
- machineconfig/jobs/python_custom_installers/wezterm.py +0 -41
- machineconfig-1.9.dist-info/RECORD +0 -76
- {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/LICENSE +0 -0
- {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/WHEEL +0 -0
- {machineconfig-1.9.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
|
|
|
@@ -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:
|
|
@@ -198,12 +211,12 @@ def display_options(msg: str, options: Iterable[T], header: str = "", tail: str
|
|
|
198
211
|
elif custom_input:
|
|
199
212
|
return choice_string # type: ignore #TODO: fix this
|
|
200
213
|
else: raise ValueError(f"Unknown choice. {choice_string}") from te
|
|
201
|
-
print(f"{choice_idx}: {choice_one}",
|
|
214
|
+
print(f"{choice_idx}: {choice_one}", "<<<<-------- CHOICE MADE")
|
|
202
215
|
if multi: return [choice_one]
|
|
203
216
|
return choice_one
|
|
204
217
|
|
|
205
218
|
|
|
206
|
-
def
|
|
219
|
+
def symlink_func(this: P, to_this: P, prioritize_to_this: bool=True):
|
|
207
220
|
"""helper function. creates a symlink from `this` to `to_this`.
|
|
208
221
|
What can go wrong?
|
|
209
222
|
depending on this and to_this existence, one will be prioretized depending on overwrite value.
|
|
@@ -222,14 +235,32 @@ def symlink(this: P, to_this: P, prioritize_to_this: bool = True):
|
|
|
222
235
|
else: this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
|
|
223
236
|
else: # this doesn't exist.
|
|
224
237
|
if not to_this.exists(): to_this.touch() # we have to touch it (file) or create it (folder)
|
|
225
|
-
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.
|
|
226
238
|
try:
|
|
227
239
|
# print(f"Linking {this} ➡️ {to_this}")
|
|
228
240
|
P(this).symlink_to(target=to_this, verbose=True, overwrite=True)
|
|
229
241
|
except Exception as ex: print(f"Failed at linking {this} ➡️ {to_this}.\nReason: {ex}")
|
|
230
242
|
|
|
231
243
|
|
|
232
|
-
def
|
|
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)
|
|
260
|
+
except Exception as ex: print(f"Failed at linking {this} ➡️ {to_this}.\nReason: {ex}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_shell_script_executing_python_file(python_file: str, func: Optional[str] = None, ve_name: str="ve", strict_execution: bool=True):
|
|
233
264
|
if func is None: exec_line = f"""python {python_file}"""
|
|
234
265
|
else: exec_line = f"""python -m fire {python_file} {func}"""
|
|
235
266
|
shell_script = f"""
|
|
@@ -247,7 +278,15 @@ deactivate || true
|
|
|
247
278
|
return shell_script
|
|
248
279
|
|
|
249
280
|
|
|
250
|
-
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):
|
|
251
290
|
if verbose:
|
|
252
291
|
python_script = f"""
|
|
253
292
|
code = r'''{python_script}'''
|
|
@@ -257,15 +296,12 @@ try:
|
|
|
257
296
|
except ImportError: print(code)
|
|
258
297
|
""" + python_script
|
|
259
298
|
python_file = P.tmp().joinpath("tmp_scripts", "python", randstr() + ".py").create(parents_only=True).write_text(python_script)
|
|
260
|
-
shell_script = get_shell_script_executing_python_file(python_file=python_file.
|
|
261
|
-
|
|
262
|
-
elif platform.system() == "Windows": suffix = ".ps1"
|
|
263
|
-
else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
264
|
-
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)
|
|
265
301
|
return shell_file
|
|
266
302
|
|
|
267
303
|
|
|
268
|
-
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):
|
|
269
305
|
if preserve_cwd:
|
|
270
306
|
if platform.system() == "Windows":
|
|
271
307
|
program = "$orig_path = $pwd\n" + program + "\ncd $orig_path"
|
|
@@ -274,13 +310,13 @@ def write_shell_script(program: str, desc: str = "", preserve_cwd: bool = True,
|
|
|
274
310
|
if display:
|
|
275
311
|
print(f"Executing {PROGRAM_PATH}")
|
|
276
312
|
print_code(code=program, lexer="shell", desc=desc)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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)
|
|
280
316
|
return None
|
|
281
317
|
|
|
282
318
|
|
|
283
|
-
def print_code(code: str, lexer: str, desc: str
|
|
319
|
+
def print_code(code: str, lexer: str, desc: str=""):
|
|
284
320
|
if lexer == "shell":
|
|
285
321
|
if platform.system() == "Windows": lexer = "powershell"
|
|
286
322
|
elif platform.system() == "Linux": lexer = "sh"
|
|
@@ -314,7 +350,7 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
314
350
|
|
|
315
351
|
try:
|
|
316
352
|
_tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
|
|
317
|
-
res: bool
|
|
353
|
+
res: bool=True
|
|
318
354
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
319
355
|
res = False
|
|
320
356
|
if res is False and install_script is not None:
|
|
@@ -327,13 +363,12 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
327
363
|
def get_ssh_hosts() -> list[str]:
|
|
328
364
|
from paramiko import SSHConfig
|
|
329
365
|
c = SSHConfig()
|
|
330
|
-
c.parse(open(P.home().joinpath(".ssh/config").
|
|
366
|
+
c.parse(open(P.home().joinpath(".ssh/config").to_str(), encoding="utf-8"))
|
|
331
367
|
return list(c.get_hostnames())
|
|
332
|
-
def choose_ssh_host(multi: bool
|
|
333
|
-
|
|
368
|
+
def choose_ssh_host(multi: bool=True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
|
|
334
369
|
|
|
335
370
|
|
|
336
|
-
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool
|
|
371
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool=False):
|
|
337
372
|
dotfiles_path = str(P.home().joinpath("dotfiles"))
|
|
338
373
|
from git import Repo
|
|
339
374
|
repo = Repo(path=dotfiles_path)
|
|
@@ -345,10 +380,54 @@ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool = False):
|
|
|
345
380
|
if res is False and update is True:
|
|
346
381
|
print(f"Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}")
|
|
347
382
|
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
348
|
-
main(cloud=None, path=dotfiles_path
|
|
383
|
+
main(cloud=None, path=dotfiles_path)
|
|
349
384
|
return res
|
|
350
385
|
|
|
351
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
|
+
|
|
352
431
|
if __name__ == '__main__':
|
|
353
432
|
# import typer
|
|
354
433
|
# typer.run(check_tool_exists)
|