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.

Files changed (53) hide show
  1. machineconfig/__init__.py +1 -1
  2. machineconfig/jobs/python/check_installations.py +7 -5
  3. machineconfig/jobs/python/checkout_version.py +27 -32
  4. machineconfig/jobs/python/create_bootable_media.py +1 -1
  5. machineconfig/jobs/python/python_cargo_build_share.py +2 -2
  6. machineconfig/jobs/python/tasks.py +2 -2
  7. machineconfig/jobs/python_custom_installers/{helix.py → hx.py} +17 -5
  8. machineconfig/profile/create.py +23 -21
  9. machineconfig/profile/create_hardlinks.py +101 -0
  10. machineconfig/profile/shell.py +5 -5
  11. machineconfig/scripts/python/cloud_copy.py +19 -16
  12. machineconfig/scripts/python/cloud_repo_sync.py +92 -47
  13. machineconfig/scripts/python/cloud_sync.py +70 -63
  14. machineconfig/scripts/python/croshell.py +6 -6
  15. machineconfig/scripts/python/devops.py +19 -20
  16. machineconfig/scripts/python/devops_backup_retrieve.py +19 -10
  17. machineconfig/scripts/python/devops_devapps_install.py +80 -62
  18. machineconfig/scripts/python/devops_update_repos.py +5 -5
  19. machineconfig/scripts/python/dotfile.py +4 -4
  20. machineconfig/scripts/python/fire_jobs.py +57 -23
  21. machineconfig/scripts/python/gh_models.py +53 -0
  22. machineconfig/scripts/python/mount_nfs.py +1 -1
  23. machineconfig/scripts/python/mount_nw_drive.py +3 -3
  24. machineconfig/scripts/python/mount_ssh.py +2 -3
  25. machineconfig/scripts/python/pomodoro.py +1 -1
  26. machineconfig/scripts/python/repos.py +21 -22
  27. machineconfig/scripts/python/scheduler.py +1 -1
  28. machineconfig/scripts/python/start_slidev.py +10 -4
  29. machineconfig/scripts/python/start_terminals.py +5 -4
  30. machineconfig/scripts/python/wifi_conn.py +34 -42
  31. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  32. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +1 -1
  33. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -2
  34. machineconfig/utils/installer.py +78 -39
  35. machineconfig/utils/procs.py +2 -2
  36. machineconfig/utils/scheduling.py +3 -3
  37. machineconfig/utils/utils.py +131 -52
  38. machineconfig/utils/ve.py +145 -95
  39. {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/METADATA +160 -155
  40. machineconfig-1.91.dist-info/RECORD +69 -0
  41. machineconfig/jobs/python_custom_installers/azuredatastudio.py +0 -36
  42. machineconfig/jobs/python_custom_installers/bypass_paywall.py +0 -30
  43. machineconfig/jobs/python_custom_installers/docker_desktop.py +0 -52
  44. machineconfig/jobs/python_custom_installers/goes.py +0 -35
  45. machineconfig/jobs/python_custom_installers/lvim.py +0 -48
  46. machineconfig/jobs/python_custom_installers/ngrok.py +0 -39
  47. machineconfig/jobs/python_custom_installers/nvim.py +0 -48
  48. machineconfig/jobs/python_custom_installers/vscode.py +0 -45
  49. machineconfig/jobs/python_custom_installers/wezterm.py +0 -41
  50. machineconfig-1.9.dist-info/RECORD +0 -76
  51. {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/LICENSE +0 -0
  52. {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/WHEEL +0 -0
  53. {machineconfig-1.9.dist-info → machineconfig-1.91.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,9 @@
3
3
  Utils
4
4
  """
5
5
 
6
- from crocodile.file_management import P, randstr
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().str.lower(), P.home().str)
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
- from crocodile.core import List as L
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(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)
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
- 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)
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
- choice = choose_one_option(msg=f"Search results are ambiguous or non-existent", options=search_results.list, fzf=True)
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(root, search_parent_directories=True)
97
+ repo = Repo(search_root_obj, search_parent_directories=True)
91
98
  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.
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
- root = repo_root_dir
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
- search_res = subprocess.run(f"cd '{root}'; fzf --filter={sub_string}", stdout=subprocess.PIPE, text=True, check=True, shell=True).stdout.split("\n")[:-1]
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 {root}.\n{cpe}")
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
- if len(search_res) == 1: return root.joinpath(search_res[0])
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 '{root}'; fzf --query={sub_string}", check=True, stdout=subprocess.PIPE, text=True, shell=True).stdout.strip()
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 {root}. {cpe}")
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 root.joinpath(res)
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 = "", tail: str = "", prompt: str = "", msg: str = "",
122
- default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
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 = "", tail: str = "", prompt: str = "", msg: str = "",
130
- default: Optional[T] = None, custom_input: bool = False) -> list[T]:
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 = "", tail: str = "", prompt: str = "",
139
- default: Optional[T] = None, fzf: bool = False, multi: bool = False, custom_input: bool = False) -> Union[T, list[T]]:
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(f" <<<<-------- DEFAULT", style="bold red")
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(f"Default option not available!")
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, f"🧨 Default option not available!"
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}", f"<<<<-------- CHOICE MADE")
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 symlink(this: P, to_this: P, prioritize_to_this: bool = True):
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 get_shell_script_executing_python_file(python_file: str, func: Optional[str] = None, ve_name: str = "ve", strict_execution: bool = True):
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 get_shell_file_executing_python_script(python_script: str, ve_name: str = "ve", verbose: bool = True):
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.str, ve_name=ve_name)
261
- if platform.system() == "Linux": suffix = ".sh"
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 = "", preserve_cwd: bool = True, display: bool = True, execute: bool = False):
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
- if platform.system() == 'Windows': PROGRAM_PATH.create(parents_only=True).write_text(program)
278
- else: PROGRAM_PATH.create(parents_only=True).write_text(f"{program}")
279
- if execute: Terminal().run(f". {PROGRAM_PATH}", shell="powershell").print_if_unsuccessful(desc="Executing shell script", strict_err=True, strict_returncode=True)
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 = True
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").str, encoding="utf-8"))
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 = True): return display_options(msg="", options=get_ssh_hosts(), multi=multi, fzf=True)
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 = False):
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, push=False)
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)