machineconfig 2.0__py3-none-any.whl → 2.1__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 (235) hide show
  1. machineconfig/cluster/cloud_manager.py +0 -3
  2. machineconfig/cluster/data_transfer.py +0 -1
  3. machineconfig/cluster/file_manager.py +0 -1
  4. machineconfig/cluster/job_params.py +0 -3
  5. machineconfig/cluster/loader_runner.py +0 -3
  6. machineconfig/cluster/remote_machine.py +0 -1
  7. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  8. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +3 -5
  9. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  10. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
  11. machineconfig/cluster/sessions_managers/wt_local.py +36 -110
  12. machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
  13. machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
  14. machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
  15. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
  16. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
  17. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
  18. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
  19. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
  20. machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
  21. machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
  22. machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
  23. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
  24. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
  25. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
  26. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
  27. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
  28. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
  29. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
  30. machineconfig/cluster/templates/cli_click.py +0 -1
  31. machineconfig/cluster/templates/cli_gooey.py +0 -2
  32. machineconfig/cluster/templates/cli_trogon.py +0 -1
  33. machineconfig/cluster/templates/run_cloud.py +0 -1
  34. machineconfig/cluster/templates/run_cluster.py +0 -1
  35. machineconfig/cluster/templates/run_remote.py +0 -1
  36. machineconfig/cluster/templates/utils.py +26 -10
  37. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  38. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  39. machineconfig/jobs/python/check_installations.py +1 -0
  40. machineconfig/jobs/python/create_bootable_media.py +0 -2
  41. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  42. machineconfig/jobs/python/tasks.py +0 -1
  43. machineconfig/jobs/python/vscode/api.py +5 -5
  44. machineconfig/jobs/python/vscode/link_ve.py +13 -14
  45. machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
  46. machineconfig/jobs/python/vscode/sync_code.py +9 -13
  47. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  48. machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
  49. machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
  50. machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
  51. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
  52. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
  53. machineconfig/jobs/python_custom_installers/dev/code.py +14 -21
  54. machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
  55. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
  56. machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
  57. machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
  58. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
  59. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
  60. machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
  61. machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
  62. machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
  63. machineconfig/jobs/python_custom_installers/docker.py +12 -21
  64. machineconfig/jobs/python_custom_installers/gh.py +11 -19
  65. machineconfig/jobs/python_custom_installers/hx.py +32 -16
  66. machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
  67. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  68. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  69. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  70. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  71. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  72. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  73. machineconfig/profile/create.py +29 -22
  74. machineconfig/profile/create_hardlinks.py +26 -19
  75. machineconfig/profile/shell.py +51 -28
  76. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  77. machineconfig/scripts/cloud/init.sh +2 -2
  78. machineconfig/scripts/linux/checkout_versions +1 -1
  79. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  80. machineconfig/scripts/linux/cloud_copy +1 -1
  81. machineconfig/scripts/linux/cloud_manager +1 -1
  82. machineconfig/scripts/linux/cloud_mount +1 -1
  83. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  84. machineconfig/scripts/linux/cloud_sync +1 -1
  85. machineconfig/scripts/linux/croshell +1 -1
  86. machineconfig/scripts/linux/devops +4 -6
  87. machineconfig/scripts/linux/fire +1 -1
  88. machineconfig/scripts/linux/fire_agents +3 -2
  89. machineconfig/scripts/linux/ftpx +1 -1
  90. machineconfig/scripts/linux/gh_models +1 -1
  91. machineconfig/scripts/linux/kill_process +1 -1
  92. machineconfig/scripts/linux/mcinit +1 -1
  93. machineconfig/scripts/linux/repos +1 -1
  94. machineconfig/scripts/linux/scheduler +1 -1
  95. machineconfig/scripts/linux/start_slidev +1 -1
  96. machineconfig/scripts/linux/start_terminals +1 -1
  97. machineconfig/scripts/linux/url2md +1 -1
  98. machineconfig/scripts/linux/warp-cli.sh +122 -0
  99. machineconfig/scripts/linux/wifi_conn +1 -1
  100. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/ai/__init__.py +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/ai/generate_files.py +84 -0
  111. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
  112. machineconfig/scripts/python/ai/mcinit.py +7 -3
  113. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
  114. machineconfig/scripts/python/cloud_copy.py +1 -1
  115. machineconfig/scripts/python/cloud_mount.py +1 -1
  116. machineconfig/scripts/python/cloud_repo_sync.py +4 -4
  117. machineconfig/scripts/python/croshell.py +5 -3
  118. machineconfig/scripts/python/devops_add_identity.py +1 -1
  119. machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
  120. machineconfig/scripts/python/devops_backup_retrieve.py +1 -1
  121. machineconfig/scripts/python/devops_update_repos.py +140 -52
  122. machineconfig/scripts/python/dotfile.py +1 -1
  123. machineconfig/scripts/python/fire_agents.py +28 -9
  124. machineconfig/scripts/python/fire_jobs.py +3 -4
  125. machineconfig/scripts/python/ftpx.py +2 -1
  126. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  127. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  128. machineconfig/scripts/python/helpers/helpers2.py +2 -2
  129. machineconfig/scripts/python/helpers/helpers4.py +1 -2
  130. machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
  131. machineconfig/scripts/python/mount_nfs.py +1 -1
  132. machineconfig/scripts/python/mount_ssh.py +1 -1
  133. machineconfig/scripts/python/repos.py +1 -1
  134. machineconfig/scripts/python/start_slidev.py +1 -1
  135. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  136. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  137. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  138. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  139. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  140. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  141. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  142. machineconfig/scripts/windows/croshell.ps1 +2 -2
  143. machineconfig/scripts/windows/devops.ps1 +1 -4
  144. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  145. machineconfig/scripts/windows/fire.ps1 +1 -1
  146. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  147. machineconfig/scripts/windows/gpt.ps1 +1 -1
  148. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  149. machineconfig/scripts/windows/mcinit.ps1 +1 -1
  150. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  151. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  152. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  153. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  154. machineconfig/scripts/windows/repos.ps1 +1 -1
  155. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  156. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  157. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  158. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  159. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  160. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  161. machineconfig/settings/lf/linux/lfrc +1 -1
  162. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  163. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  164. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  165. machineconfig/settings/svim/linux/init.toml +1 -1
  166. machineconfig/settings/svim/windows/init.toml +1 -1
  167. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
  168. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  169. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  170. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  171. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  172. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  173. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  174. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +66 -12
  175. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +44 -36
  176. machineconfig/utils/ai/generate_file_checklist.py +8 -10
  177. machineconfig/utils/ai/url2md.py +4 -2
  178. machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
  179. machineconfig/utils/cloud/onedrive/transaction.py +63 -98
  180. machineconfig/utils/code.py +60 -39
  181. machineconfig/utils/installer.py +27 -33
  182. machineconfig/utils/installer_utils/installer_abc.py +8 -7
  183. machineconfig/utils/installer_utils/installer_class.py +149 -70
  184. machineconfig/utils/links.py +22 -11
  185. machineconfig/utils/notifications.py +197 -0
  186. machineconfig/utils/options.py +29 -23
  187. machineconfig/utils/path.py +13 -6
  188. machineconfig/utils/path_reduced.py +485 -216
  189. machineconfig/utils/procs.py +47 -41
  190. machineconfig/utils/scheduling.py +0 -1
  191. machineconfig/utils/ssh.py +157 -76
  192. machineconfig/utils/terminal.py +82 -37
  193. machineconfig/utils/utils.py +12 -10
  194. machineconfig/utils/utils2.py +38 -48
  195. machineconfig/utils/utils5.py +183 -116
  196. machineconfig/utils/ve.py +9 -4
  197. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/METADATA +3 -2
  198. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/RECORD +200 -217
  199. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  200. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  201. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  202. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  203. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  204. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  205. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  206. machineconfig/jobs/python_generic_installers/update.py +0 -3
  207. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  208. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  209. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  210. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  211. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  212. machineconfig/scripts/linux/activate_ve +0 -87
  213. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  214. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  215. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  216. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  217. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  218. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  219. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  220. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  221. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  222. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  223. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  224. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  225. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  226. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  227. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
  228. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  229. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  230. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  231. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  232. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  233. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  234. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
  235. {machineconfig-2.0.dist-info → machineconfig-2.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,4 @@
1
-
2
- from machineconfig.utils.path_reduced import P, OPLike
1
+ from machineconfig.utils.path_reduced import PathExtended, OPLike
3
2
  import subprocess
4
3
  from typing import Any, BinaryIO, Optional, Union
5
4
  import platform
@@ -29,48 +28,70 @@ class Response:
29
28
  resp.output.stderr = cp.stderr
30
29
  resp.output.returncode = cp.returncode
31
30
  return resp
31
+
32
32
  def __init__(self, stdin: Optional[BinaryIO] = None, stdout: Optional[BinaryIO] = None, stderr: Optional[BinaryIO] = None, cmd: Optional[str] = None, desc: str = ""):
33
33
  self.std = dict(stdin=stdin, stdout=stdout, stderr=stderr)
34
34
  self.output = STD(stdin="", stdout="", stderr="", returncode=0)
35
35
  self.input = cmd
36
36
  self.desc = desc # input command
37
+
37
38
  def __call__(self, *args: Any, **kwargs: Any) -> Optional[str]:
38
39
  _ = args, kwargs
39
40
  return self.op.rstrip() if type(self.op) is str else None
41
+
40
42
  @property
41
- def op(self) -> str: return self.output.stdout
43
+ def op(self) -> str:
44
+ return self.output.stdout
45
+
42
46
  @property
43
- def ip(self) -> str: return self.output.stdin
47
+ def ip(self) -> str:
48
+ return self.output.stdin
49
+
44
50
  @property
45
- def err(self) -> str: return self.output.stderr
51
+ def err(self) -> str:
52
+ return self.output.stderr
53
+
46
54
  @property
47
- def returncode(self) -> int: return self.output.returncode
48
- def op2path(self, strict_returncode: bool = True, strict_err: bool = False) -> Union[P, None]:
49
- if self.is_successful(strict_returcode=strict_returncode, strict_err=strict_err): return P(self.op.rstrip())
55
+ def returncode(self) -> int:
56
+ return self.output.returncode
57
+
58
+ def op2path(self, strict_returncode: bool = True, strict_err: bool = False) -> Union[PathExtended, None]:
59
+ if self.is_successful(strict_returcode=strict_returncode, strict_err=strict_err):
60
+ return PathExtended(self.op.rstrip())
50
61
  return None
51
- def op_if_successfull_or_default(self, strict_returcode: bool = True, strict_err: bool = False) -> Optional[str]: return self.op if self.is_successful(strict_returcode=strict_returcode, strict_err=strict_err) else None
62
+
63
+ def op_if_successfull_or_default(self, strict_returcode: bool = True, strict_err: bool = False) -> Optional[str]:
64
+ return self.op if self.is_successful(strict_returcode=strict_returcode, strict_err=strict_err) else None
65
+
52
66
  def is_successful(self, strict_returcode: bool = True, strict_err: bool = False) -> bool:
53
67
  return ((self.returncode in {0, None}) if strict_returcode else True) and (self.err == "" if strict_err else True)
68
+
54
69
  def capture(self):
55
70
  for key in ["stdin", "stdout", "stderr"]:
56
71
  val: Optional[BinaryIO] = self.std[key]
57
72
  if val is not None and val.readable():
58
73
  self.output.__dict__[key] = val.read().decode().rstrip()
59
74
  return self
75
+
60
76
  def print_if_unsuccessful(self, desc: str = "TERMINAL CMD", strict_err: bool = False, strict_returncode: bool = False, assert_success: bool = False):
61
77
  success = self.is_successful(strict_err=strict_err, strict_returcode=strict_returncode)
62
- if assert_success: assert success, self.print(capture=False, desc=desc)
78
+ if assert_success:
79
+ assert success, self.print(capture=False, desc=desc)
63
80
  if success:
64
81
  print(f"✅ {desc} completed successfully")
65
82
  else:
66
83
  self.print(capture=False, desc=desc)
67
84
  return self
85
+
68
86
  def print(self, desc: str = "TERMINAL CMD", capture: bool = True):
69
- if capture: self.capture()
87
+ if capture:
88
+ self.capture()
70
89
  from rich import console
90
+
71
91
  con = console.Console()
72
92
  from rich.panel import Panel
73
93
  from rich.text import Text # from rich.syntax import Syntax; syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True)
94
+
74
95
  tmp1 = Text("📥 Input Command:\n")
75
96
  tmp1.stylize("u bold blue")
76
97
  tmp2 = Text("\n📤 Terminal Response:\n")
@@ -80,10 +101,12 @@ class Response:
80
101
  con.print(Panel(txt, title=f"🖥️ {self.desc}", subtitle=f"📋 {desc}", width=150, style="bold cyan on black"))
81
102
  return self
82
103
 
104
+
83
105
  # DEPRECATED: Use subprocess.run directly instead of Terminal class.
84
106
  # The Terminal class has been replaced with inline subprocess calls to underlying primitives.
85
107
  # This file is kept for reference but should not be used.
86
108
 
109
+
87
110
  class Terminal:
88
111
  def __init__(self, stdout: Optional[int] = subprocess.PIPE, stderr: Optional[int] = subprocess.PIPE, stdin: Optional[int] = subprocess.PIPE, elevated: bool = False):
89
112
  self.machine: str = platform.system()
@@ -91,88 +114,110 @@ class Terminal:
91
114
  self.stdout = stdout
92
115
  self.stderr = stderr
93
116
  self.stdin = stdin
117
+
94
118
  # def set_std_system(self): self.stdout = sys.stdout; self.stderr = sys.stderr; self.stdin = sys.stdin
95
119
  def set_std_pipe(self):
96
120
  self.stdout = subprocess.PIPE
97
121
  self.stderr = subprocess.PIPE
98
122
  self.stdin = subprocess.PIPE
123
+
99
124
  def set_std_null(self):
100
125
  self.stdout, self.stderr, self.stdin = subprocess.DEVNULL, subprocess.DEVNULL, subprocess.DEVNULL # Equivalent to `echo 'foo' &> /dev/null`
126
+
101
127
  def run(self, *cmds: str, shell: Optional[SHELLS] = "default", check: bool = False, ip: Optional[str] = None) -> Response: # Runs SYSTEM commands like subprocess.run
102
128
  """Blocking operation. Thus, if you start a shell via this method, it will run in the main and won't stop until you exit manually IF stdin is set to sys.stdin, otherwise it will run and close quickly. Other combinations of stdin, stdout can lead to funny behaviour like no output but accept input or opposite.
103
129
  * This method is short for: res = subprocess.run("powershell command", capture_output=True, shell=True, text=True) and unlike os.system(cmd), subprocess.run(cmd) gives much more control over the output and input.
104
130
  * `shell=True` loads up the profile of the shell called so more specific commands can be run. Importantly, on Windows, the `start` command becomes availalbe and new windows can be launched.
105
131
  * `capture_output` prevents the stdout to redirect to the stdout of the script automatically, instead it will be stored in the Response object returned. # `capture_output=True` same as `stdout=subprocess.PIPE, stderr=subprocess.PIPE`"""
106
- my_list = list(cmds) # `subprocess.Popen` (process open) is the most general command. Used here to create asynchronous job. `subprocess.run` is a thin wrapper around Popen that makes it wait until it finishes the task. `suprocess.call` is an archaic command for pre-Python-3.5.
107
- if self.machine == "Windows" and shell in {"powershell", "pwsh"}: my_list = [shell, "-Command"] + my_list # alternatively, one can run "cmd"
132
+ my_list = list(
133
+ cmds
134
+ ) # `subprocess.Popen` (process open) is the most general command. Used here to create asynchronous job. `subprocess.run` is a thin wrapper around Popen that makes it wait until it finishes the task. `suprocess.call` is an archaic command for pre-Python-3.5.
135
+ if self.machine == "Windows" and shell in {"powershell", "pwsh"}:
136
+ my_list = [shell, "-Command"] + my_list # alternatively, one can run "cmd"
108
137
  if self.elevated is False or self.is_user_admin():
109
138
  resp: subprocess.CompletedProcess[str] = subprocess.run(my_list, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=check, input=ip)
110
139
  else:
111
140
  resp = __import__("ctypes").windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
112
141
  return Response.from_completed_process(resp)
142
+
113
143
  def run_script(self, script: str, shell: SHELLS = "default", verbose: bool = False):
114
- if self.machine == "Linux": script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
115
- script_file = P.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
144
+ if self.machine == "Linux":
145
+ script = "#!/bin/bash" + "\n" + script # `source` is only available in bash.
146
+ script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if self.machine == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline={"Windows": None, "Linux": "\n"}[self.machine])
116
147
  if shell == "default":
117
148
  if self.machine == "Windows":
118
149
  start_cmd = "powershell" # default shell on Windows is cmd which is not very useful. (./source is not available)
119
150
  full_command: Union[list[str], str] = [start_cmd, str(script_file)] # shell=True will cause this to be a string anyway (with space separation)
120
151
  else:
121
- start_cmd = "bash"
152
+ start_cmd = "bash"
122
153
  full_command = f"{start_cmd} {script_file}" # full_command = [start_cmd, str(script_file)]
123
- else: full_command = f"{shell} {script_file}" # full_command = [shell, str(tmp_file)]
154
+ else:
155
+ full_command = f"{shell} {script_file}" # full_command = [shell, str(tmp_file)]
124
156
  if verbose:
125
- desc="Script to be executed:"
126
- if platform.system() == "Windows": lexer = "powershell"
127
- elif platform.system() == "Linux": lexer = "sh"
128
- elif platform.system() == "Darwin": lexer = "sh" # macOS uses similar shell to Linux
129
- else: raise NotImplementedError(f"Platform {platform.system()} not supported.")
157
+ desc = "Script to be executed:"
158
+ if platform.system() == "Windows":
159
+ lexer = "powershell"
160
+ elif platform.system() == "Linux":
161
+ lexer = "sh"
162
+ elif platform.system() == "Darwin":
163
+ lexer = "sh" # macOS uses similar shell to Linux
164
+ else:
165
+ raise NotImplementedError(f"Platform {platform.system()} not supported.")
130
166
  from rich.console import Console
131
167
  from rich.panel import Panel
132
168
  from rich.syntax import Syntax
133
169
  import rich.progress as pb
170
+
134
171
  console = Console()
135
172
  console.print(Panel(Syntax(code=script, lexer=lexer), title=f"📄 {desc}"), style="bold red")
136
173
  with pb.Progress(transient=True) as progress:
137
174
  _task = progress.add_task(f"Running Script @ {script_file}", total=None)
138
175
  resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
139
- else: resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
176
+ else:
177
+ resp = subprocess.run(full_command, stderr=self.stderr, stdin=self.stdin, stdout=self.stdout, text=True, shell=True, check=False)
140
178
  return Response.from_completed_process(resp)
179
+
141
180
  def run_py(self, script: str, wdir: OPLike = None, interactive: bool = True, ipython: bool = True, shell: Optional[str] = None, terminal: str = "", new_window: bool = True, header: bool = True): # async run, since sync run is meaningless.
142
181
  script = (Terminal.get_header(wdir=wdir, toolbox=True) if header else "") + script + ("\nDisplayData.set_pandas_auto_width()\n" if terminal in {"wt", "powershell", "pwsh"} else "")
143
- py_script = P.tmpfile(name="tmp_python_script", suffix=".py", folder="tmp_scripts/terminal")
182
+ py_script = PathExtended.tmpfile(name="tmp_python_script", suffix=".py", folder="tmp_scripts/terminal")
144
183
  py_script.write_text(f"""print(r'''{script}''')""" + "\n" + script)
145
184
  print(f"""🚀 [ASYNC PYTHON SCRIPT] Script URI:
146
185
  {py_script.absolute().as_uri()}""")
147
186
  print("Script to be executed asyncronously: ", py_script.absolute().as_uri())
148
187
  shell_script = f"""
149
- {f'cd {wdir}' if wdir is not None else ''}
150
- {'ipython' if ipython else 'python'} {'-i' if interactive else ''} {py_script}
188
+ {f"cd {wdir}" if wdir is not None else ""}
189
+ {"ipython" if ipython else "python"} {"-i" if interactive else ""} {py_script}
151
190
  """
152
- shell_script = P.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell").write_text(shell_script)
153
- if shell is None and self.machine == "Windows": shell = "pwsh"
191
+ shell_script = PathExtended.tmpfile(name="tmp_shell_script", suffix=".sh" if self.machine == "Linux" else ".ps1", folder="tmp_scripts/shell").write_text(shell_script)
192
+ if shell is None and self.machine == "Windows":
193
+ shell = "pwsh"
154
194
  window = "start" if new_window and self.machine == "Windows" else ""
155
195
  os.system(f"{window} {terminal} {shell} {shell_script}")
196
+
156
197
  @staticmethod
157
198
  def is_user_admin() -> bool: # adopted from: https://stackoverflow.com/questions/19672352/how-to-run-script-with-elevated-privilege-on-windows"""
158
- if os.name == 'nt':
159
- try: return __import__("ctypes").windll.shell32.IsUserAnAdmin()
199
+ if os.name == "nt":
200
+ try:
201
+ return __import__("ctypes").windll.shell32.IsUserAnAdmin()
160
202
  except Exception:
161
203
  import traceback
204
+
162
205
  traceback.print_exc()
163
206
  print("Admin check failed, assuming not an admin.")
164
207
  return False
165
208
  else:
166
209
  return os.getuid() == 0 # Check for root on Posix
167
- # @staticmethod
168
- # def run_as_admin(file: PLike, params: Any, wait: bool = False):
169
- # proce_info = install_n_import(library="win32com", package="pywin32", fromlist=["shell.shell.ShellExecuteEx"]).shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=file, lpParameters=params)
170
- # # TODO update PATH for this to take effect immediately.
171
- # if wait: time.sleep(1)
172
- # return proce_info
210
+
211
+ # @staticmethod
212
+ # def run_as_admin(file: PLike, params: Any, wait: bool = False):
213
+ # proce_info = install_n_import(library="win32com", package="pywin32", fromlist=["shell.shell.ShellExecuteEx"]).shell.shell.ShellExecuteEx(lpVerb='runas', lpFile=file, lpParameters=params)
214
+ # # TODO update PATH for this to take effect immediately.
215
+ # if wait: time.sleep(1)
216
+ # return proce_info
173
217
 
174
218
  @staticmethod
175
- def get_header(wdir: OPLike, toolbox: bool): return f"""
219
+ def get_header(wdir: OPLike, toolbox: bool):
220
+ return f"""
176
221
  # >> Code prepended
177
222
  {"from crocodile.toolbox import *" if toolbox else "# No toolbox import."}
178
223
  {'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
@@ -2,17 +2,17 @@
2
2
  Utils
3
3
  """
4
4
 
5
- from machineconfig.utils.path_reduced import P as PathExtended
5
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
6
  import machineconfig
7
7
  from machineconfig.utils.options import check_tool_exists, choose_cloud_interactively, choose_multiple_options, choose_one_option, choose_ssh_host, display_options
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
10
  from machineconfig.utils.links import build_links, symlink_copy, symlink_func
11
- from machineconfig.utils.code import get_shell_script_executing_python_file, get_shell_file_executing_python_script, write_shell_script_to_default_program_path, print_code, PROGRAM_PATH
11
+ from machineconfig.utils.code import write_shell_script_to_default_program_path, print_code, PROGRAM_PATH
12
12
  from machineconfig.utils.path import sanitize_path, match_file_name
13
13
 
14
14
  # Split into multiple assignments to fix incompatible tuple sizes
15
- _ = get_shell_script_executing_python_file, get_shell_file_executing_python_script, print_code, PROGRAM_PATH, display_options, write_shell_script_to_default_program_path
15
+ _ = print_code, PROGRAM_PATH, display_options, write_shell_script_to_default_program_path
16
16
  _ = build_links
17
17
  _ = symlink_copy
18
18
  _ = symlink_func
@@ -47,31 +47,34 @@ DEFAULTS_PATH = PathExtended.home().joinpath("dotfiles/machineconfig/defaults.in
47
47
  # else: print(f"\n❌ ERROR | API request failed: {response.status_code}\n")
48
48
 
49
49
 
50
-
51
-
52
50
  def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
53
51
  dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
54
52
  from git import Repo
53
+
55
54
  repo = Repo(path=dotfiles_path)
56
55
  last_commit = repo.head.commit
57
56
  dtm = last_commit.committed_datetime
58
57
  from datetime import datetime # make it tz unaware
58
+
59
59
  dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
60
- res = dtm > datetime.fromisoformat(commit_dtm)
60
+ res = dtm > datetime.fromisoformat(commit_dtm)
61
61
  if res is False and update is True:
62
62
  console = Console()
63
63
  console.print(Panel(f"🔄 UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
64
64
  from machineconfig.scripts.python.cloud_repo_sync import main
65
+
65
66
  main(cloud=None, path=dotfiles_path)
66
67
  return res
67
68
 
69
+
68
70
  def wait_for_jobs_to_finish(root: PathExtended, pattern: str, wait_for_n_jobs: int, max_wait_minutes: float) -> bool:
69
- wait_finished: bool=False
71
+ wait_finished: bool = False
70
72
  import time
73
+
71
74
  t0 = time.time()
72
75
  while not wait_finished:
73
76
  parts = root.search(pattern, folders=False, r=False)
74
- counter = len(parts)
77
+ counter = len(parts)
75
78
  if counter == wait_for_n_jobs:
76
79
  wait_finished = True
77
80
  console = Console()
@@ -88,8 +91,7 @@ def wait_for_jobs_to_finish(root: PathExtended, pattern: str, wait_for_n_jobs: i
88
91
  return False
89
92
 
90
93
 
91
-
92
- if __name__ == '__main__':
94
+ if __name__ == "__main__":
93
95
  # import typer
94
96
  # typer.run(check_tool_exists)
95
97
  pass
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  from pathlib import Path
4
2
  from typing import Optional, Any
5
3
  # import time
@@ -9,77 +7,69 @@ from typing import Optional, Any
9
7
  def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bool = True, punctuation: bool = False, safe: bool = False, noun: bool = False) -> str:
10
8
  if safe:
11
9
  import secrets
10
+
12
11
  return secrets.token_urlsafe(length) # interannly, it uses: random.SystemRandom or os.urandom which is hardware-based, not pseudo
13
12
  if noun:
14
13
  import randomname
14
+
15
15
  return randomname.get_name()
16
16
  import string
17
17
  import random
18
+
18
19
  population = (string.ascii_lowercase if lower else "") + (string.ascii_uppercase if upper else "") + (string.digits if digits else "") + (string.punctuation if punctuation else "")
19
- return ''.join(random.choices(population, k=length))
20
+ return "".join(random.choices(population, k=length))
20
21
 
21
22
 
22
- def read_ini(path: 'Path', encoding: Optional[str] = None):
23
- if not Path(path).exists() or Path(path).is_dir(): raise FileNotFoundError(f"File not found or is a directory: {path}")
23
+ def read_ini(path: "Path", encoding: Optional[str] = None):
24
+ if not Path(path).exists() or Path(path).is_dir():
25
+ raise FileNotFoundError(f"File not found or is a directory: {path}")
24
26
  import configparser
27
+
25
28
  res = configparser.ConfigParser()
26
29
  res.read(filenames=[str(path)], encoding=encoding)
27
30
  return res
28
- def read_json(path: 'Path', r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
31
+
32
+
33
+ def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
29
34
  import json
35
+
30
36
  try:
31
- mydict = json.loads(Path(path).read_text(encoding='utf-8'), **kwargs)
37
+ mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
32
38
  except Exception:
33
39
  import pyjson5
34
- mydict = pyjson5.loads(Path(path).read_text(encoding='utf-8'), **kwargs) # file has C-style comments.
40
+
41
+ mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
35
42
  _ = r
36
43
  return mydict
37
- def read_toml(path: 'Path'):
44
+
45
+
46
+ def read_toml(path: "Path"):
38
47
  import tomli
39
- return tomli.loads(path.read_text(encoding='utf-8'))
48
+
49
+ return tomli.loads(path.read_text(encoding="utf-8"))
50
+
40
51
 
41
52
  def pprint(obj: dict[Any, Any], title: str) -> None:
42
53
  from rich import inspect
43
54
  inspect(type("TempStruct", (object,), obj)(), value=False, title=title, docs=False, dunder=False, sort=False)
55
+
56
+
44
57
  def get_repr(obj: dict[Any, Any], sep: str = "\n", justify: int = 15, quotes: bool = False):
45
58
  return sep.join([f"{key:>{justify}} = {repr(val) if quotes else val}" for key, val in obj.items()])
46
59
 
47
60
 
48
- # T = TypeVar('T')
49
- # PS = ParamSpec('PS')
50
-
51
- # class RepeatUntilNoException:
52
- # """
53
- # Repeat function calling if it raised an exception and/or exceeded the timeout, for a maximum of `retry` times.
54
- # * Alternative: `https://github.com/jd/tenacity`
55
- # """
56
- # def __init__(self, retry: int, sleep: float, timeout: Optional[float] = None, scaling: Literal["linear", "exponential"] = "exponential"):
57
- # self.retry = retry
58
- # self.sleep = sleep
59
- # self.timeout = timeout
60
- # self.scaling: Literal["linear", "exponential"] = scaling
61
- # def __call__(self, func: Callable[PS, T]) -> Callable[PS, T]:
62
- # from functools import wraps
63
- # if self.timeout is not None:
64
- # import warpt_time_decorator
65
- # func = wrapt_timeout_decorator.timeout(self.timeout)(func)
66
- # @wraps(wrapped=func)
67
- # def wrapper(*args: PS.args, **kwargs: PS.kwargs):
68
- # t0 = time.time()
69
- # for idx in range(self.retry):
70
- # try:
71
- # return func(*args, **kwargs)
72
- # except Exception as ex:
73
- # match self.scaling:
74
- # case "linear":
75
- # sleep_time = self.sleep * (idx + 1)
76
- # case "exponential":
77
- # sleep_time = self.sleep * (idx + 1)**2
78
- # print(f"""💥 [RETRY] Function {func.__name__} call failed with error:
79
- # {ex}
80
- # Retry count: {idx}/{self.retry}. Sleeping for {sleep_time} seconds.
81
- # Total elapsed time: {time.time() - t0:0.1f} seconds.""")
82
- # print(f"""💥 Robust call of `{func}` failed with ```{ex}```.\nretrying {idx}/{self.retry} more times after sleeping for {sleep_time} seconds.\nTotal wait time so far {time.time() - t0: 0.1f} seconds.""")
83
- # time.sleep(sleep_time)
84
- # raise RuntimeError(f"💥 Robust call failed after {self.retry} retries and total wait time of {time.time() - t0: 0.1f} seconds.\n{func=}\n{args=}\n{kwargs=}")
85
- # return wrapper
61
+ def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
62
+ from datetime import datetime
63
+
64
+ result = {}
65
+ for k, v in d.items():
66
+ if isinstance(v, float):
67
+ result[k] = f"{v:.2f}"
68
+ elif isinstance(v, bool):
69
+ result[k] = "" if v else ""
70
+ elif isinstance(v, int) and len(str(v)) == 13 and v > 0: # assuming ms timestamp
71
+ dt = datetime.fromtimestamp(v / 1000)
72
+ result[k] = dt.strftime("%Y-%m-%d %H:%M")
73
+ else:
74
+ result[k] = v
75
+ return result