machineconfig 1.9__py3-none-any.whl → 1.92__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 +2 -4
- machineconfig/jobs/python/check_installations.py +14 -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/{helix.py → hx.py} +21 -6
- machineconfig/profile/create.py +23 -21
- machineconfig/profile/create_hardlinks.py +101 -0
- machineconfig/profile/shell.py +10 -7
- machineconfig/scripts/python/cloud_copy.py +19 -16
- machineconfig/scripts/python/cloud_repo_sync.py +94 -47
- machineconfig/scripts/python/cloud_sync.py +78 -61
- machineconfig/scripts/python/croshell.py +6 -6
- machineconfig/scripts/python/devops.py +22 -22
- machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
- 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 +115 -63
- machineconfig/scripts/python/gh_models.py +55 -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 +22 -23
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +12 -6
- 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/settings/__init__.py +0 -0
- 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 +86 -41
- machineconfig/utils/procs.py +2 -2
- machineconfig/utils/scheduling.py +3 -3
- machineconfig/utils/utils.py +136 -56
- machineconfig/utils/ve.py +145 -95
- {machineconfig-1.9.dist-info → machineconfig-1.92.dist-info}/METADATA +160 -155
- machineconfig-1.92.dist-info/RECORD +70 -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.92.dist-info}/LICENSE +0 -0
- {machineconfig-1.9.dist-info → machineconfig-1.92.dist-info}/WHEEL +0 -0
- {machineconfig-1.9.dist-info → machineconfig-1.92.dist-info}/top_level.txt +0 -0
|
@@ -62,7 +62,7 @@ class Report:
|
|
|
62
62
|
status: str
|
|
63
63
|
|
|
64
64
|
@classmethod
|
|
65
|
-
def from_path(cls, path: P, return_default_if_not_found: bool
|
|
65
|
+
def from_path(cls, path: P, return_default_if_not_found: bool=False):
|
|
66
66
|
if not path.exists():
|
|
67
67
|
if return_default_if_not_found:
|
|
68
68
|
return Report(name=path.parent.name, start=datetime(year=2000, month=1, day=1), end=datetime(year=2000, month=1, day=1), status="NA")
|
|
@@ -108,7 +108,7 @@ def read_task_from_dir(path: P):
|
|
|
108
108
|
return task
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
def main(root: Optional[str] = None, ignore_conditions: bool
|
|
111
|
+
def main(root: Optional[str] = None, ignore_conditions: bool=True):
|
|
112
112
|
if root is None: root_resolved = SCHEDULER_DEFAULT_ROOT
|
|
113
113
|
else: root_resolved = P(root).expanduser().absolute()
|
|
114
114
|
tasks_dirs = root_resolved.search(files=False, folders=True).filter(lambda x: x.joinpath("task.py").exists())
|
|
@@ -168,7 +168,7 @@ def should_task_run(task: Task, tolerance_mins: int = 1440) -> tuple[bool, Optio
|
|
|
168
168
|
def run_task(task: Task) -> Report:
|
|
169
169
|
start_time = datetime.now()
|
|
170
170
|
|
|
171
|
-
shell_script = get_shell_script_executing_python_file(python_file=task.task_root.joinpath("task.py").
|
|
171
|
+
shell_script = get_shell_script_executing_python_file(python_file=task.task_root.joinpath("task.py").to_str(), ve_name=task.venv)
|
|
172
172
|
shell_script_root = P.tmp().joinpath(f"tmp_scripts/scheduler/{task.name}").create()
|
|
173
173
|
try:
|
|
174
174
|
if platform.system() == 'Windows':
|
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,13 +17,12 @@ 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
|
-
PROGRAM_PATH = (P.
|
|
22
|
+
PROGRAM_PATH = (P.home().joinpath("tmp_results", "shells", "python_return_command") + (".ps1" if platform.system() == "Windows" else ".sh"))
|
|
23
23
|
CONFIG_PATH = P.home().joinpath(".config/machineconfig")
|
|
24
24
|
INSTALL_VERSION_ROOT = CONFIG_PATH.joinpath("cli_tools_installers/versions")
|
|
25
|
-
INSTALL_TMP_DIR = P.
|
|
26
|
-
|
|
25
|
+
INSTALL_TMP_DIR = P.home().joinpath("tmp_results", "tmp_installers")
|
|
27
26
|
DEFAULTS_PATH = P.home().joinpath("dotfiles/machineconfig/defaults.ini")
|
|
28
27
|
|
|
29
28
|
|
|
@@ -31,8 +30,7 @@ T = TypeVar("T")
|
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def choose_cloud_interactively() -> str:
|
|
34
|
-
|
|
35
|
-
print(f"Listing Remotes ... ")
|
|
33
|
+
print("Listing Remotes ... ")
|
|
36
34
|
tmp = Terminal().run("rclone listremotes").op_if_successfull_or_default(strict_returcode=False)
|
|
37
35
|
# consider this: remotes = Read.ini(P.home().joinpath(".config/rclone/rclone.conf")).sections()
|
|
38
36
|
if isinstance(tmp, str):
|
|
@@ -40,8 +38,8 @@ def choose_cloud_interactively() -> str:
|
|
|
40
38
|
|
|
41
39
|
else: raise ValueError(f"Got {tmp} from rclone listremotes")
|
|
42
40
|
if len(remotes) == 0:
|
|
43
|
-
raise RuntimeError(
|
|
44
|
-
cloud: str
|
|
41
|
+
raise RuntimeError("You don't have remotes. Configure your rclone first to get cloud services access.")
|
|
42
|
+
cloud: str=choose_one_option(msg="WHICH CLOUD?", options=list(remotes), default=remotes[0], fzf=True)
|
|
45
43
|
return cloud
|
|
46
44
|
|
|
47
45
|
|
|
@@ -71,63 +69,78 @@ def sanitize_path(a_path: P) -> P:
|
|
|
71
69
|
|
|
72
70
|
def match_file_name(sub_string: str, search_root: Optional[P] = None) -> P:
|
|
73
71
|
"""Look up current directory for file name that matches the passed substring."""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
search_root_obj = search_root if search_root is not None else P.cwd()
|
|
73
|
+
search_root_obj = search_root_obj.absolute()
|
|
74
|
+
print(f"Searching for {sub_string} in {search_root_obj}")
|
|
75
|
+
|
|
76
|
+
search_root_objects = search_root_obj.search("*", not_in=["links", ".venv", ".git", ".idea", ".vscode", "node_modules", "__pycache__"])
|
|
77
|
+
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
|
|
78
|
+
search_results = search_results.filter(lambda x: x.suffix in (".py", ".sh", ".ps1"))
|
|
79
|
+
|
|
77
80
|
if len(search_results) == 1:
|
|
78
81
|
path_obj = search_results.list[0]
|
|
79
82
|
elif len(search_results) > 1:
|
|
80
|
-
|
|
83
|
+
msg = "Search results are ambiguous or non-existent, choose manually:"
|
|
84
|
+
print(msg)
|
|
85
|
+
choice = choose_one_option(msg=msg, options=search_results.list, fzf=True)
|
|
81
86
|
path_obj = P(choice)
|
|
82
87
|
else:
|
|
83
88
|
# let's do a final retry with sub_string.small()
|
|
84
89
|
sub_string_small = sub_string.lower()
|
|
85
90
|
if sub_string_small != sub_string:
|
|
91
|
+
print("Retrying with small letters")
|
|
86
92
|
return match_file_name(sub_string=sub_string_small)
|
|
87
93
|
from git.repo import Repo
|
|
88
94
|
from git.exc import InvalidGitRepositoryError
|
|
89
95
|
try:
|
|
90
|
-
repo = Repo(
|
|
96
|
+
repo = Repo(search_root_obj, search_parent_directories=True)
|
|
91
97
|
repo_root_dir = P(repo.working_dir)
|
|
92
|
-
if repo_root_dir !=
|
|
98
|
+
if repo_root_dir != search_root_obj: # may be user is in a subdirectory of the repo root, try with root dir.
|
|
99
|
+
print("Retrying with root repo instea of cwd")
|
|
93
100
|
return match_file_name(sub_string=sub_string, search_root=repo_root_dir)
|
|
94
101
|
else:
|
|
95
|
-
|
|
102
|
+
search_root_obj = repo_root_dir
|
|
96
103
|
except InvalidGitRepositoryError:
|
|
97
104
|
pass
|
|
98
105
|
|
|
99
|
-
if check_tool_exists("fzf"):
|
|
106
|
+
if check_tool_exists(tool_name="fzf"):
|
|
100
107
|
try:
|
|
101
|
-
|
|
108
|
+
print(f"Using fd to searching for `{sub_string}` in `{search_root_obj}` ...")
|
|
109
|
+
fzf_cmd = f"cd '{search_root_obj}'; fd --type f --strip-cwd-prefix | fzf --filter={sub_string}"
|
|
110
|
+
search_res = subprocess.run(fzf_cmd, stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout.split("\n")[:-1]
|
|
102
111
|
except subprocess.CalledProcessError as cpe:
|
|
103
|
-
print(f"Failed at fzf search with {sub_string} in {
|
|
112
|
+
print(f"Failed at fzf search with {sub_string} in {search_root_obj}.\n{cpe}")
|
|
104
113
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
105
114
|
raise FileNotFoundError(msg) from cpe
|
|
106
|
-
|
|
115
|
+
|
|
116
|
+
if len(search_res) == 1: return search_root_obj.joinpath(search_res[0])
|
|
107
117
|
else:
|
|
118
|
+
print("Tring with raw fzf search ...")
|
|
108
119
|
try:
|
|
109
|
-
res = subprocess.run(f"cd '{
|
|
120
|
+
# res = subprocess.run(f"cd '{search_root_obj}'; fzf --query={sub_string}", check=True, stdout=subprocess.PIPE, text=True, shell=True).stdout.strip()
|
|
121
|
+
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
122
|
except subprocess.CalledProcessError as cpe:
|
|
111
|
-
print(f"Failed at fzf search with {sub_string} in {
|
|
123
|
+
print(f"Failed at fzf search with {sub_string} in {search_root_obj}. {cpe}")
|
|
112
124
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
113
125
|
raise FileNotFoundError(msg) from cpe
|
|
114
|
-
return
|
|
126
|
+
return search_root_obj.joinpath(res)
|
|
127
|
+
|
|
115
128
|
msg = f"\n{'--' * 50}\n💥 Path {sub_string} does not exist. No search results\n{'--' * 50}\n"
|
|
116
129
|
raise FileNotFoundError(msg)
|
|
117
130
|
print(f"\n{'--' * 50}\n🔗 Matched `{sub_string}` ➡️ `{path_obj}`\n{'--' * 50}\n")
|
|
118
131
|
return path_obj
|
|
119
132
|
|
|
120
133
|
|
|
121
|
-
def choose_one_option(options: Iterable[T], header: str
|
|
122
|
-
default: Optional[T] = None, fzf: bool
|
|
134
|
+
def choose_one_option(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
|
|
135
|
+
default: Optional[T] = None, fzf: bool=False, custom_input: bool=False) -> T:
|
|
123
136
|
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
124
137
|
default=default, fzf=fzf, multi=False, custom_input=custom_input)
|
|
125
138
|
assert not isinstance(choice_key, list)
|
|
126
139
|
return choice_key
|
|
127
140
|
|
|
128
141
|
|
|
129
|
-
def choose_multiple_options(options: Iterable[T], header: str
|
|
130
|
-
default: Optional[T] = None, custom_input: bool
|
|
142
|
+
def choose_multiple_options(options: Iterable[T], header: str="", tail: str="", prompt: str="", msg: str="",
|
|
143
|
+
default: Optional[T] = None, custom_input: bool=False) -> list[T]:
|
|
131
144
|
choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt,
|
|
132
145
|
default=default, fzf=True, multi=True,
|
|
133
146
|
custom_input=custom_input)
|
|
@@ -135,14 +148,14 @@ def choose_multiple_options(options: Iterable[T], header: str = "", tail: str =
|
|
|
135
148
|
return [choice_key]
|
|
136
149
|
|
|
137
150
|
|
|
138
|
-
def display_options(msg: str, options: Iterable[T], header: str
|
|
139
|
-
default: Optional[T] = None, fzf: bool
|
|
140
|
-
# TODO: replace with https://github.com/tmbo/questionary
|
|
151
|
+
def display_options(msg: str, options: Iterable[T], header: str="", tail: str="", prompt: str="",
|
|
152
|
+
default: Optional[T] = None, fzf: bool=False, multi: bool=False, custom_input: bool=False) -> Union[T, list[T]]:
|
|
153
|
+
# TODO: replace with https://github.com/tmbo/questionary
|
|
154
|
+
# # 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,13 @@ 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):
|
|
305
|
+
def write_shell_script(program: str, desc: str, preserve_cwd: bool, display: bool, execute: bool):
|
|
269
306
|
if preserve_cwd:
|
|
270
307
|
if platform.system() == "Windows":
|
|
271
308
|
program = "$orig_path = $pwd\n" + program + "\ncd $orig_path"
|
|
@@ -274,13 +311,13 @@ def write_shell_script(program: str, desc: str = "", preserve_cwd: bool = True,
|
|
|
274
311
|
if display:
|
|
275
312
|
print(f"Executing {PROGRAM_PATH}")
|
|
276
313
|
print_code(code=program, lexer="shell", desc=desc)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
314
|
+
PROGRAM_PATH.create(parents_only=True).write_text(program)
|
|
315
|
+
if execute:
|
|
316
|
+
Terminal().run(f". {PROGRAM_PATH}", shell="powershell").print_if_unsuccessful(desc="Executing shell script", strict_err=True, strict_returncode=True)
|
|
280
317
|
return None
|
|
281
318
|
|
|
282
319
|
|
|
283
|
-
def print_code(code: str, lexer: str, desc: str
|
|
320
|
+
def print_code(code: str, lexer: str, desc: str):
|
|
284
321
|
if lexer == "shell":
|
|
285
322
|
if platform.system() == "Windows": lexer = "powershell"
|
|
286
323
|
elif platform.system() == "Linux": lexer = "sh"
|
|
@@ -314,7 +351,7 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
314
351
|
|
|
315
352
|
try:
|
|
316
353
|
_tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
|
|
317
|
-
res: bool
|
|
354
|
+
res: bool=True
|
|
318
355
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
319
356
|
res = False
|
|
320
357
|
if res is False and install_script is not None:
|
|
@@ -327,13 +364,12 @@ def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> b
|
|
|
327
364
|
def get_ssh_hosts() -> list[str]:
|
|
328
365
|
from paramiko import SSHConfig
|
|
329
366
|
c = SSHConfig()
|
|
330
|
-
c.parse(open(P.home().joinpath(".ssh/config").
|
|
367
|
+
c.parse(open(P.home().joinpath(".ssh/config").to_str(), encoding="utf-8"))
|
|
331
368
|
return list(c.get_hostnames())
|
|
332
|
-
def choose_ssh_host(multi: bool
|
|
333
|
-
|
|
369
|
+
def choose_ssh_host(multi: bool=True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
|
|
334
370
|
|
|
335
371
|
|
|
336
|
-
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool
|
|
372
|
+
def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool=False):
|
|
337
373
|
dotfiles_path = str(P.home().joinpath("dotfiles"))
|
|
338
374
|
from git import Repo
|
|
339
375
|
repo = Repo(path=dotfiles_path)
|
|
@@ -345,10 +381,54 @@ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool = False):
|
|
|
345
381
|
if res is False and update is True:
|
|
346
382
|
print(f"Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}")
|
|
347
383
|
from machineconfig.scripts.python.cloud_repo_sync import main
|
|
348
|
-
main(cloud=None, path=dotfiles_path
|
|
384
|
+
main(cloud=None, path=dotfiles_path)
|
|
349
385
|
return res
|
|
350
386
|
|
|
351
387
|
|
|
388
|
+
def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
|
|
389
|
+
"""Build symboic links from various relevant paths (e.g. data) to `repo_root/links/<name>` to facilitate easy access from
|
|
390
|
+
tree explorer of the IDE.
|
|
391
|
+
"""
|
|
392
|
+
target_dirs_filtered: list[tuple[P, str]] = []
|
|
393
|
+
for a_dir, a_name in target_paths:
|
|
394
|
+
a_dir_obj = P(a_dir).resolve()
|
|
395
|
+
if not a_dir_obj.exists():
|
|
396
|
+
a_dir_obj.mkdir(parents=True, exist_ok=True)
|
|
397
|
+
target_dirs_filtered.append((a_dir_obj, a_name))
|
|
398
|
+
|
|
399
|
+
import git
|
|
400
|
+
repo = git.Repo(repo_root, search_parent_directories=True)
|
|
401
|
+
root_maybe = repo.working_tree_dir
|
|
402
|
+
assert root_maybe is not None
|
|
403
|
+
repo_root_obj = P(root_maybe)
|
|
404
|
+
tmp_results_root = P.home().joinpath("tmp_results", "tmp_data", repo_root_obj.name)
|
|
405
|
+
tmp_results_root.mkdir(parents=True, exist_ok=True)
|
|
406
|
+
target_dirs_filtered.append((tmp_results_root, "tmp_results"))
|
|
407
|
+
|
|
408
|
+
for a_target_path, a_name in target_dirs_filtered:
|
|
409
|
+
links_path = repo_root_obj.joinpath("links", a_name)
|
|
410
|
+
links_path.symlink_to(target=a_target_path)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def wait_for_jobs_to_finish(root: P, pattern: str, wait_for_n_jobs: int, max_wait_minutes: float) -> bool:
|
|
414
|
+
wait_finished: bool=False
|
|
415
|
+
import time
|
|
416
|
+
t0 = time.time()
|
|
417
|
+
while not wait_finished:
|
|
418
|
+
parts = root.search(pattern, folders=False, r=False)
|
|
419
|
+
counter = len(parts)
|
|
420
|
+
if counter == wait_for_n_jobs:
|
|
421
|
+
wait_finished = True
|
|
422
|
+
print(f"{counter} Jobs finished. Exiting.")
|
|
423
|
+
return True
|
|
424
|
+
if (time.time() - t0) > 60 * max_wait_minutes:
|
|
425
|
+
print(f"Waited for {max_wait_minutes} minutes. Exiting.")
|
|
426
|
+
return False
|
|
427
|
+
print(f"{counter} Jobs finished. Waiting for {wait_for_n_jobs - counter} / {wait_for_n_jobs} jobs to finish, sleeping for 60 seconds.")
|
|
428
|
+
time.sleep(60)
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
|
|
352
432
|
if __name__ == '__main__':
|
|
353
433
|
# import typer
|
|
354
434
|
# typer.run(check_tool_exists)
|