machineconfig 2.1__py3-none-any.whl → 2.3__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 (127) hide show
  1. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
  2. machineconfig/cluster/sessions_managers/layout_types.py +29 -0
  3. machineconfig/cluster/sessions_managers/wt_local.py +68 -62
  4. machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
  5. machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
  6. machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
  7. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
  8. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
  9. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
  10. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
  11. machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
  12. machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
  13. machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
  14. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
  15. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
  16. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
  17. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
  18. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
  19. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
  20. machineconfig/cluster/templates/utils.py +0 -35
  21. machineconfig/jobs/python/check_installations.py +1 -1
  22. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  23. machineconfig/jobs/python_generic_installers/config.json +1 -1
  24. machineconfig/profile/create.py +13 -4
  25. machineconfig/profile/create_hardlinks.py +3 -1
  26. machineconfig/profile/shell.py +8 -7
  27. machineconfig/scripts/__init__.py +0 -2
  28. machineconfig/scripts/linux/devops +6 -4
  29. machineconfig/scripts/python/ai/generate_files.py +14 -15
  30. machineconfig/scripts/python/ai/mcinit.py +8 -5
  31. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  32. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  33. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  34. machineconfig/scripts/python/cloud_copy.py +22 -13
  35. machineconfig/scripts/python/cloud_mount.py +35 -23
  36. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  37. machineconfig/scripts/python/cloud_sync.py +4 -4
  38. machineconfig/scripts/python/croshell.py +37 -28
  39. machineconfig/scripts/python/devops.py +46 -27
  40. machineconfig/scripts/python/devops_add_identity.py +15 -25
  41. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  42. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  43. machineconfig/scripts/python/devops_devapps_install.py +26 -20
  44. machineconfig/scripts/python/devops_update_repos.py +142 -57
  45. machineconfig/scripts/python/dotfile.py +16 -14
  46. machineconfig/scripts/python/fire_agents.py +30 -23
  47. machineconfig/scripts/python/fire_jobs.py +86 -98
  48. machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
  49. machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
  50. machineconfig/scripts/python/ftpx.py +24 -14
  51. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  52. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  53. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  54. machineconfig/scripts/python/helpers/helpers4.py +44 -31
  55. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  56. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  57. machineconfig/scripts/python/mount_nfs.py +8 -15
  58. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  59. machineconfig/scripts/python/mount_ssh.py +8 -6
  60. machineconfig/scripts/python/repos.py +215 -57
  61. machineconfig/scripts/python/snapshot.py +0 -1
  62. machineconfig/scripts/python/start_slidev.py +10 -5
  63. machineconfig/scripts/python/start_terminals.py +22 -16
  64. machineconfig/scripts/python/viewer_template.py +0 -1
  65. machineconfig/scripts/python/wifi_conn.py +49 -76
  66. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  67. machineconfig/settings/lf/linux/lfrc +1 -0
  68. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  70. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  71. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  72. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  73. machineconfig/utils/code.py +2 -3
  74. machineconfig/utils/installer.py +2 -2
  75. machineconfig/utils/installer_utils/installer_abc.py +2 -4
  76. machineconfig/utils/installer_utils/installer_class.py +6 -4
  77. machineconfig/utils/links.py +103 -33
  78. machineconfig/utils/notifications.py +52 -38
  79. machineconfig/utils/options.py +14 -21
  80. machineconfig/utils/path.py +12 -12
  81. machineconfig/utils/path_reduced.py +239 -200
  82. machineconfig/utils/procs.py +1 -1
  83. machineconfig/utils/source_of_truth.py +27 -0
  84. machineconfig/utils/ssh.py +9 -19
  85. machineconfig/utils/terminal.py +4 -2
  86. machineconfig/utils/upgrade_packages.py +91 -0
  87. machineconfig/utils/utils2.py +1 -2
  88. machineconfig/utils/utils5.py +23 -11
  89. machineconfig/utils/ve.py +4 -1
  90. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
  91. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
  92. machineconfig-2.3.dist-info/entry_points.txt +2 -0
  93. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
  94. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
  95. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  96. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  97. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  98. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  99. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  100. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +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/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  112. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  113. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  114. machineconfig/utils/utils.py +0 -97
  115. /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
  116. /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
  117. /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
  118. /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
  119. /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
  120. /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
  121. /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
  122. /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
  123. /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
  124. /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
  125. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  126. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
  127. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  from machineconfig.utils.utils2 import randstr
2
+
2
3
  from datetime import datetime
3
4
  import time
4
5
  from pathlib import Path
@@ -6,8 +7,6 @@ import sys
6
7
  import subprocess
7
8
  from platform import system
8
9
  from typing import Any, Optional, Union, Callable, TypeAlias, Literal
9
- import os
10
- # import warnings
11
10
 
12
11
 
13
12
  OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
@@ -101,129 +100,6 @@ def timestamp(fmt: Optional[str] = None, name: Optional[str] = None) -> str:
101
100
  return ((name + "_") if name is not None else "") + datetime.now().strftime(fmt or "%Y-%m-%d-%I-%M-%S-%p-%f") # isoformat is not compatible with file naming convention, fmt here is.
102
101
 
103
102
 
104
- def modify_text(txt_raw: str, txt_search: str, txt_alt: Union[str, Callable[[str], str]], replace_line: bool = True, notfound_append: bool = False, prepend: bool = False, strict: bool = False):
105
- lines, bingo = txt_raw.split("\n"), False
106
- if not replace_line: # no need for line splitting
107
- assert isinstance(txt_alt, str), f"txt_alt must be a string if notfound_append is True. It is not: {txt_alt}"
108
- if txt_search in txt_raw:
109
- return txt_raw.replace(txt_search, txt_alt)
110
- return txt_raw + "\n" + txt_alt if notfound_append else txt_raw
111
- for idx, line in enumerate(lines):
112
- if txt_search in line:
113
- if isinstance(txt_alt, str):
114
- lines[idx] = txt_alt
115
- elif callable(txt_alt):
116
- lines[idx] = txt_alt(line)
117
- bingo = True
118
- if strict and not bingo:
119
- raise ValueError(f"txt_search `{txt_search}` not found in txt_raw `{txt_raw}`")
120
- if bingo is False and notfound_append is True:
121
- assert isinstance(txt_alt, str), f"txt_alt must be a string if notfound_append is True. It is not: {txt_alt}"
122
- if prepend:
123
- lines.insert(0, txt_alt)
124
- else:
125
- lines.append(txt_alt) # txt not found, add it anyway.
126
- return "\n".join(lines)
127
-
128
-
129
- class Compression:
130
- @staticmethod
131
- def compress_folder(
132
- root_dir: str, op_path: str, base_dir: str, fmt: SHUTIL_FORMATS = "zip", verbose: bool = False, **kwargs: Any
133
- ) -> str: # shutil works with folders nicely (recursion is done interally) # directory to be archived: root_dir\base_dir, unless base_dir is passed as absolute path. # when archive opened; base_dir will be found."""
134
- base_name = op_path[:-4] if op_path.endswith(".zip") else op_path # .zip is added automatically by library, hence we'd like to avoid repeating it if user sent it.
135
- import shutil
136
-
137
- return shutil.make_archive(base_name=base_name, format=fmt, root_dir=root_dir, base_dir=base_dir, verbose=verbose, **kwargs) # returned path possible have added extension.
138
-
139
- @staticmethod
140
- def zip_file(ip_path: str, op_path: str, arcname: Optional[str] = None, password: Optional[bytes] = None, mode: FILE_MODE = "w", **kwargs: Any):
141
- """arcname determines the directory of the file being archived inside the archive. Defaults to same as original directory except for drive.
142
- When changed, it should still include the file path in its end. If arcname = filename without any path, then, it will be in the root of the archive."""
143
- import zipfile
144
-
145
- with zipfile.ZipFile(op_path, mode=mode) as jungle_zip:
146
- if password is not None:
147
- jungle_zip.setpassword(pwd=password)
148
- jungle_zip.write(filename=str(ip_path), arcname=str(arcname) if arcname is not None else None, compress_type=zipfile.ZIP_DEFLATED, **kwargs)
149
- return Path(op_path)
150
-
151
- @staticmethod
152
- def unzip(ip_path: str, op_path: str, fname: Optional[str] = None, password: Optional[bytes] = None, memory: bool = False, **kwargs: Any) -> Path | dict[str, bytes] | bytes:
153
- import zipfile
154
-
155
- with zipfile.ZipFile(str(ip_path), "r") as zipObj:
156
- if memory:
157
- return {name: zipObj.read(name) for name in zipObj.namelist()} if fname is None else zipObj.read(fname)
158
- if fname is None:
159
- zipObj.extractall(op_path, pwd=password, **kwargs)
160
- return Path(op_path)
161
- else:
162
- zipObj.extract(member=str(fname), path=str(op_path), pwd=password)
163
- return Path(op_path) / fname
164
-
165
- @staticmethod
166
- def gz(file: str, op_path: str): # see this on what to use: https://stackoverflow.com/questions/10540935/what-is-the-difference-between-tar-and-zip
167
- import shutil
168
- import gzip
169
-
170
- with open(file, "rb") as f_in:
171
- with gzip.open(op_path, "wb") as f_out:
172
- shutil.copyfileobj(f_in, f_out)
173
- return Path(op_path)
174
-
175
- @staticmethod
176
- def ungz(path: str, op_path: str):
177
- import gzip
178
- import shutil
179
-
180
- with gzip.open(path, "r") as f_in, open(op_path, "wb") as f_out:
181
- shutil.copyfileobj(f_in, f_out)
182
- return Path(op_path)
183
-
184
- @staticmethod
185
- def unbz(path: str, op_path: str):
186
- import bz2
187
- import shutil
188
-
189
- with bz2.BZ2File(path, "r") as fr, open(str(op_path), "wb") as fw:
190
- shutil.copyfileobj(fr, fw)
191
- return Path(op_path)
192
-
193
- @staticmethod
194
- def xz(path: str, op_path: str):
195
- import lzma
196
-
197
- with lzma.open(op_path, "w") as f:
198
- f.write(Path(path).read_bytes())
199
-
200
- @staticmethod
201
- def unxz(ip_path: str, op_path: str):
202
- import lzma
203
-
204
- with lzma.open(ip_path) as file:
205
- Path(op_path).write_bytes(file.read())
206
-
207
- @staticmethod
208
- def tar(path: str, op_path: str):
209
- import tarfile
210
-
211
- with tarfile.open(op_path, "w:gz") as tar_:
212
- tar_.add(str(path), arcname=os.path.basename(path))
213
- return Path(op_path)
214
-
215
- @staticmethod
216
- def untar(path: str, op_path: str, fname: Optional[str] = None, mode: Literal["r", "w"] = "r", **kwargs: Any):
217
- import tarfile
218
-
219
- with tarfile.open(str(path), mode) as file:
220
- if fname is None:
221
- file.extractall(path=op_path, **kwargs) # extract all files in the archive
222
- else:
223
- file.extract(fname, **kwargs)
224
- return Path(op_path)
225
-
226
-
227
103
  class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
228
104
  # ============= Path management ==================
229
105
  """The default behaviour of methods acting on underlying disk object is to perform the action and return a new path referring to the mutated object in disk drive.
@@ -327,39 +203,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
327
203
  dest_path.write_bytes(response.content)
328
204
  return dest_path
329
205
 
330
- def _return(
331
- self, res: Union["PathExtended", "Path"], operation: Literal["rename", "delete", "Whack"], inplace: bool = False, overwrite: bool = False, orig: bool = False, verbose: bool = False, strict: bool = True, msg: str = "", __delayed_msg__: str = ""
332
- ) -> "PathExtended":
333
- res = PathExtended(res)
334
- if inplace:
335
- assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
336
- if operation == "rename":
337
- if overwrite and res.exists():
338
- res.delete(sure=True, verbose=verbose)
339
- if not overwrite and res.exists():
340
- if strict:
341
- raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
342
- else:
343
- if verbose:
344
- print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
345
- return self if orig else res
346
- self.rename(res)
347
- msg = msg or f"RENAMED {repr(self)} ➡️ {repr(res)}"
348
- elif operation == "delete":
349
- self.delete(sure=True, verbose=False)
350
- __delayed_msg__ = f"DELETED 🗑️❌ {repr(self)}."
351
- if verbose and msg != "":
352
- try:
353
- print(msg) # emojie print error.
354
- except UnicodeEncodeError:
355
- print("P._return warning: UnicodeEncodeError, could not print message.")
356
- if verbose and __delayed_msg__ != "":
357
- try:
358
- print(__delayed_msg__)
359
- except UnicodeEncodeError:
360
- print("P._return warning: UnicodeEncodeError, could not print message.")
361
- return self if orig else res
362
-
363
206
  def append(self, name: str = "", index: bool = False, suffix: Optional[str] = None, verbose: bool = True, **kwargs: Any) -> "PathExtended":
364
207
  """Returns a new path object with the name appended to the stem of the path. If `index` is True, the name will be the index of the path in the parent directory."""
365
208
  if index:
@@ -368,10 +211,59 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
368
211
  full_name = name or ("_" + str(timestamp()))
369
212
  full_suffix = suffix or "".join(("bruh" + self).suffixes)
370
213
  subpath = self.name.split(".")[0] + full_name + full_suffix
371
- return self._return(self.parent.joinpath(subpath), operation="rename", verbose=verbose, **kwargs)
214
+ dest = self.parent.joinpath(subpath)
215
+ res = PathExtended(dest)
216
+ inplace = bool(kwargs.get("inplace", False))
217
+ overwrite = bool(kwargs.get("overwrite", False))
218
+ orig = bool(kwargs.get("orig", False))
219
+ strict = bool(kwargs.get("strict", True))
220
+ if inplace:
221
+ assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
222
+ if overwrite and res.exists():
223
+ res.delete(sure=True, verbose=verbose)
224
+ if not overwrite and res.exists():
225
+ if strict:
226
+ raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
227
+ else:
228
+ if verbose:
229
+ try:
230
+ print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
231
+ except UnicodeEncodeError:
232
+ print("P._return warning: UnicodeEncodeError, could not print message.")
233
+ return self if orig else res
234
+ self.rename(res)
235
+ if verbose:
236
+ try:
237
+ print(f"RENAMED {repr(self)} ➡️ {repr(res)}")
238
+ except UnicodeEncodeError:
239
+ print("P._return warning: UnicodeEncodeError, could not print message.")
240
+ return self if orig else res
372
241
 
373
242
  def with_name(self, name: str, verbose: bool = True, inplace: bool = False, overwrite: bool = False, **kwargs: Any):
374
- return self._return(self.parent / name, verbose=verbose, operation="rename", inplace=inplace, overwrite=overwrite, **kwargs)
243
+ res = PathExtended(self.parent / name)
244
+ orig = bool(kwargs.get("orig", False))
245
+ strict = bool(kwargs.get("strict", True))
246
+ if inplace:
247
+ assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
248
+ if overwrite and res.exists():
249
+ res.delete(sure=True, verbose=verbose)
250
+ if not overwrite and res.exists():
251
+ if strict:
252
+ raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
253
+ else:
254
+ if verbose:
255
+ try:
256
+ print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
257
+ except UnicodeEncodeError:
258
+ print("P._return warning: UnicodeEncodeError, could not print message.")
259
+ return self if orig else res
260
+ self.rename(res)
261
+ if verbose:
262
+ try:
263
+ print(f"RENAMED {repr(self)} ➡️ {repr(res)}")
264
+ except UnicodeEncodeError:
265
+ print("P._return warning: UnicodeEncodeError, could not print message.")
266
+ return self if orig else res
375
267
 
376
268
  def __deepcopy__(self, *args: Any, **kwargs: Any) -> "PathExtended":
377
269
  _ = args, kwargs
@@ -391,14 +283,14 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
391
283
  return (res[1:] if str(res[0]) in {"\\", "/"} else res) if len(res.parts) else res # paths starting with "/" are problematic. e.g ~ / "/path" doesn't work.
392
284
 
393
285
  def rel2home(self) -> "PathExtended":
394
- return self._return(PathExtended(self.expanduser().absolute().relative_to(Path.home())), operation="Whack") # very similat to collapseuser but without "~" being added so its consistent with rel2cwd.
286
+ return PathExtended(self.expanduser().absolute().relative_to(Path.home())) # very similat to collapseuser but without "~" being added so its consistent with rel2cwd.
395
287
 
396
288
  def collapseuser(self, strict: bool = True, placeholder: str = "~") -> "PathExtended": # opposite of `expanduser` resolve is crucial to fix Windows cases insensitivty problem.
397
289
  if strict:
398
290
  assert str(self.expanduser().absolute().resolve()).startswith(str(PathExtended.home())), ValueError(f"`{PathExtended.home()}` is not in the subpath of `{self}`")
399
291
  if str(self).startswith(placeholder) or PathExtended.home().as_posix() not in self.resolve().as_posix():
400
292
  return self
401
- return self._return(res=PathExtended(placeholder) / (self.expanduser().absolute().resolve(strict=strict) - PathExtended.home()), operation="Whack") # resolve also solves the problem of Windows case insensitivty.
293
+ return PathExtended(placeholder) / (self.expanduser().absolute().resolve(strict=strict) - PathExtended.home()) # resolve also solves the problem of Windows case insensitivty.
402
294
 
403
295
  def __getitem__(self, slici: Union[int, list[int], slice]):
404
296
  if isinstance(slici, list):
@@ -458,27 +350,9 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
458
350
  tmp = 1024**3
459
351
  return round(number=total_size / tmp, ndigits=1)
460
352
 
461
- # def time(self, which: Literal["m", "c", "a"] = "m", **kwargs: Any):
462
- # """* `m`: last mofidication of content, i.e. the time it was created.
463
- # * `c`: last status change (its inode is changed, permissions, path, but not content)
464
- # * `a`: last access (read)
465
- # """
466
- # warnings.warn(
467
- # "The 'time' method is deprecated. Use 'datetime.fromtimestamp(self.stat().st_mtime)' for 'm', "
468
- # "'datetime.fromtimestamp(self.stat().st_ctime)' for 'c', or "
469
- # "'datetime.fromtimestamp(self.stat().st_atime)' for 'a' instead.",
470
- # DeprecationWarning,
471
- # stacklevel=2
472
- # )
473
- # match which:
474
- # case "m": tmp = self.stat().st_mtime
475
- # case "a": tmp = self.stat().st_atime
476
- # case "c": tmp = self.stat().st_ctime
477
- # return datetime.fromtimestamp(tmp, **kwargs)
478
-
479
353
  # ================================ String Nature management ====================================
480
354
  def clickable(self) -> "PathExtended":
481
- return self._return(res=PathExtended(self.expanduser().resolve().as_uri()), operation="Whack")
355
+ return PathExtended(self.expanduser().resolve().as_uri())
482
356
 
483
357
  def as_url_str(self) -> "str":
484
358
  return self.as_posix().replace("https:/", "https://").replace("http:/", "http://")
@@ -528,7 +402,12 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
528
402
  time.sleep(1) # wait=True equivalent
529
403
  else:
530
404
  super(PathExtended, self.expanduser()).symlink_to(str(target_obj))
531
- return self._return(target_obj, operation="Whack", inplace=False, orig=orig, verbose=verbose, msg=f"LINKED {repr(self)} ➡️ {repr(target_obj)}")
405
+ if verbose:
406
+ try:
407
+ print(f"LINKED {repr(self)} ➡️ {repr(target_obj)}")
408
+ except UnicodeEncodeError:
409
+ print("P._return warning: UnicodeEncodeError, could not print message.")
410
+ return self if orig else target_obj
532
411
 
533
412
  def resolve(self, strict: bool = False):
534
413
  try:
@@ -587,7 +466,9 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
587
466
  else:
588
467
  raw = glob(str(slf.joinpath(pattern))) # glob ignroes dot and hidden files
589
468
  if ".zip" not in str(slf) and compressed:
590
- filters_notin = [PathExtended(comp_file).search(pattern=pattern, r=r, files=files, folders=folders, compressed=True, dotfiles=dotfiles, filters_total=filters_total, not_in=not_in, win_order=win_order) for comp_file in self.search("*.zip", r=r)]
469
+ filters_notin = [
470
+ PathExtended(comp_file).search(pattern=pattern, r=r, files=files, folders=folders, compressed=True, dotfiles=dotfiles, filters_total=filters_total, not_in=not_in, win_order=win_order) for comp_file in self.search("*.zip", r=r)
471
+ ]
591
472
  from functools import reduce
592
473
 
593
474
  # haha = List(filters_notin).reduce(func=lambda x, y: x + y)
@@ -644,14 +525,40 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
644
525
  if arcname_obj.name != slf.name:
645
526
  arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
646
527
  if slf.is_file():
647
- path_resolved = Compression.zip_file(ip_path=str(slf), op_path=str(path_resolved + ".zip" if path_resolved.suffix != ".zip" else path_resolved), arcname=str(arcname_obj), mode=mode, **kwargs)
528
+ import zipfile
529
+
530
+ op_zip = str(path_resolved + ".zip" if path_resolved.suffix != ".zip" else path_resolved)
531
+ with zipfile.ZipFile(op_zip, mode=mode) as jungle_zip:
532
+ jungle_zip.write(filename=str(slf), arcname=str(arcname_obj), compress_type=zipfile.ZIP_DEFLATED, **kwargs)
533
+ path_resolved = PathExtended(op_zip)
648
534
  else:
535
+ import shutil
536
+
649
537
  if content:
650
538
  root_dir, base_dir = slf, "."
651
539
  else:
652
540
  root_dir, base_dir = slf.split(at=str(arcname_obj[0]), sep=1)[0], str(arcname_obj)
653
- path_resolved = PathExtended(Compression.compress_folder(root_dir=str(root_dir), op_path=str(path_resolved), base_dir=base_dir, fmt="zip", **kwargs)) # TODO: see if this supports mode
654
- return self._return(path_resolved, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"ZIPPED {repr(slf)} ==> {repr(path)}")
541
+ base_name = str(path_resolved)[:-4] if str(path_resolved).endswith(".zip") else str(path_resolved)
542
+ op_zip = shutil.make_archive(base_name=base_name, format="zip", root_dir=str(root_dir), base_dir=str(base_dir), verbose=False, **kwargs)
543
+ path_resolved = PathExtended(op_zip)
544
+ msg = f"ZIPPED {repr(slf)} ==> {repr(path)}"
545
+ res_out = PathExtended(path_resolved)
546
+ ret = self if orig else res_out
547
+ delayed_msg = ""
548
+ if inplace:
549
+ self.delete(sure=True, verbose=False)
550
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
551
+ if verbose:
552
+ try:
553
+ print(msg)
554
+ except UnicodeEncodeError:
555
+ print("P._return warning: UnicodeEncodeError, could not print message.")
556
+ if verbose and delayed_msg != "":
557
+ try:
558
+ print(delayed_msg)
559
+ except UnicodeEncodeError:
560
+ print("P._return warning: UnicodeEncodeError, could not print message.")
561
+ return ret
655
562
 
656
563
  def unzip(
657
564
  self,
@@ -706,29 +613,127 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
706
613
  # List().apply(lambda item: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True))
707
614
  for item in mylist:
708
615
  PathExtended(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True)
709
- result = Compression.unzip(str(zipfile__), str(folder), None if name is None else PathExtended(name).as_posix())
710
- assert isinstance(result, Path)
711
- return self._return(PathExtended(result), inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNZIPPED {repr(zipfile__)} ==> {repr(result)}")
616
+ import zipfile
617
+
618
+ target_name = None if name is None else PathExtended(name).as_posix()
619
+ with zipfile.ZipFile(str(zipfile__), "r") as zipObj:
620
+ if target_name is None:
621
+ zipObj.extractall(str(folder))
622
+ result = Path(str(folder))
623
+ else:
624
+ zipObj.extract(member=str(target_name), path=str(folder))
625
+ result = Path(str(folder)) / target_name
626
+ res_path = PathExtended(result)
627
+ msg = f"UNZIPPED {repr(zipfile__)} ==> {repr(result)}"
628
+ ret = self if orig else PathExtended(res_path)
629
+ delayed_msg = ""
630
+ if inplace:
631
+ self.delete(sure=True, verbose=False)
632
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
633
+ if verbose:
634
+ try:
635
+ print(msg)
636
+ except UnicodeEncodeError:
637
+ print("P._return warning: UnicodeEncodeError, could not print message.")
638
+ if verbose and delayed_msg != "":
639
+ try:
640
+ print(delayed_msg)
641
+ except UnicodeEncodeError:
642
+ print("P._return warning: UnicodeEncodeError, could not print message.")
643
+ return ret
712
644
 
713
645
  def untar(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
714
646
  op_path = self._resolve_path(folder, name, path, self.name.replace(".tar", "")).expanduser().resolve()
715
- Compression.untar(str(self.expanduser().resolve()), op_path=str(op_path))
716
- return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNTARRED {repr(self)} ==> {repr(op_path)}")
647
+ import tarfile
648
+
649
+ with tarfile.open(str(self.expanduser().resolve()), "r") as tf:
650
+ tf.extractall(path=str(op_path))
651
+ msg = f"UNTARRED {repr(self)} ==> {repr(op_path)}"
652
+ ret = self if orig else PathExtended(op_path)
653
+ delayed_msg = ""
654
+ if inplace:
655
+ self.delete(sure=True, verbose=False)
656
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
657
+ if verbose:
658
+ try:
659
+ print(msg)
660
+ except UnicodeEncodeError:
661
+ print("P._return warning: UnicodeEncodeError, could not print message.")
662
+ if verbose and delayed_msg != "":
663
+ try:
664
+ print(delayed_msg)
665
+ except UnicodeEncodeError:
666
+ print("P._return warning: UnicodeEncodeError, could not print message.")
667
+ return ret
717
668
 
718
669
  def ungz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
719
670
  op_path = self._resolve_path(folder, name, path, self.name.replace(".gz", "")).expanduser().resolve()
720
- Compression.ungz(str(self.expanduser().resolve()), op_path=str(op_path))
721
- return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNGZED {repr(self)} ==> {repr(op_path)}")
671
+ import gzip
672
+
673
+ PathExtended(str(op_path)).write_bytes(gzip.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
674
+ msg = f"UNGZED {repr(self)} ==> {repr(op_path)}"
675
+ ret = self if orig else PathExtended(op_path)
676
+ delayed_msg = ""
677
+ if inplace:
678
+ self.delete(sure=True, verbose=False)
679
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
680
+ if verbose:
681
+ try:
682
+ print(msg)
683
+ except UnicodeEncodeError:
684
+ print("P._return warning: UnicodeEncodeError, could not print message.")
685
+ if verbose and delayed_msg != "":
686
+ try:
687
+ print(delayed_msg)
688
+ except UnicodeEncodeError:
689
+ print("P._return warning: UnicodeEncodeError, could not print message.")
690
+ return ret
722
691
 
723
692
  def unxz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
724
693
  op_path = self._resolve_path(folder, name, path, self.name.replace(".xz", "")).expanduser().resolve()
725
- Compression.unxz(str(self.expanduser().resolve()), op_path=str(op_path))
726
- return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNXZED {repr(self)} ==> {repr(op_path)}")
694
+ import lzma
695
+
696
+ PathExtended(str(op_path)).write_bytes(lzma.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
697
+ msg = f"UNXZED {repr(self)} ==> {repr(op_path)}"
698
+ ret = self if orig else PathExtended(op_path)
699
+ delayed_msg = ""
700
+ if inplace:
701
+ self.delete(sure=True, verbose=False)
702
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
703
+ if verbose:
704
+ try:
705
+ print(msg)
706
+ except UnicodeEncodeError:
707
+ print("P._return warning: UnicodeEncodeError, could not print message.")
708
+ if verbose and delayed_msg != "":
709
+ try:
710
+ print(delayed_msg)
711
+ except UnicodeEncodeError:
712
+ print("P._return warning: UnicodeEncodeError, could not print message.")
713
+ return ret
727
714
 
728
715
  def unbz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
729
716
  op_path = self._resolve_path(folder=folder, name=name, path=path, default_name=self.name.replace(".bz", "").replace(".tbz", ".tar")).expanduser().resolve()
730
- Compression.unbz(str(self.expanduser().resolve()), op_path=str(op_path))
731
- return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNBZED {repr(self)} ==> {repr(op_path)}")
717
+ import bz2
718
+
719
+ PathExtended(str(op_path)).write_bytes(bz2.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
720
+ msg = f"UNBZED {repr(self)} ==> {repr(op_path)}"
721
+ ret = self if orig else PathExtended(op_path)
722
+ delayed_msg = ""
723
+ if inplace:
724
+ self.delete(sure=True, verbose=False)
725
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
726
+ if verbose:
727
+ try:
728
+ print(msg)
729
+ except UnicodeEncodeError:
730
+ print("P._return warning: UnicodeEncodeError, could not print message.")
731
+ if verbose and delayed_msg != "":
732
+ try:
733
+ print(delayed_msg)
734
+ except UnicodeEncodeError:
735
+ print("P._return warning: UnicodeEncodeError, could not print message.")
736
+ return ret
732
737
 
733
738
  def decompress(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
734
739
  if ".tar.gz" in str(self) or ".tgz" in str(self):
@@ -748,19 +753,53 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
748
753
  res = self
749
754
  return res
750
755
 
751
- def encrypt(self, key: Optional[bytes] = None, pwd: Optional[str] = None, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, verbose: bool = True, suffix: str = ".enc", inplace: bool = False, orig: bool = False) -> "PathExtended":
756
+ def encrypt(
757
+ self, key: Optional[bytes] = None, pwd: Optional[str] = None, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, verbose: bool = True, suffix: str = ".enc", inplace: bool = False, orig: bool = False
758
+ ) -> "PathExtended":
752
759
  # see: https://stackoverflow.com/questions/42568262/how-to-encrypt-text-with-a-password-in-python & https://stackoverflow.com/questions/2490334/simple-way-to-encode-a-string-according-to-a-password"""
753
760
  slf = self.expanduser().resolve()
754
761
  path = self._resolve_path(folder, name, path, slf.name + suffix)
755
762
  assert slf.is_file(), f"Cannot encrypt a directory. You might want to try `zip_n_encrypt`. {self}"
756
763
  path.write_bytes(encrypt(msg=slf.read_bytes(), key=key, pwd=pwd))
757
- return self._return(path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"🔒🔑 ENCRYPTED: {repr(slf)} ==> {repr(path)}.")
764
+ msg = f"🔒🔑 ENCRYPTED: {repr(slf)} ==> {repr(path)}."
765
+ ret = self if orig else PathExtended(path)
766
+ delayed_msg = ""
767
+ if inplace:
768
+ self.delete(sure=True, verbose=False)
769
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
770
+ if verbose:
771
+ try:
772
+ print(msg)
773
+ except UnicodeEncodeError:
774
+ print("P._return warning: UnicodeEncodeError, could not print message.")
775
+ if verbose and delayed_msg != "":
776
+ try:
777
+ print(delayed_msg)
778
+ except UnicodeEncodeError:
779
+ print("P._return warning: UnicodeEncodeError, could not print message.")
780
+ return ret
758
781
 
759
782
  def decrypt(self, key: Optional[bytes] = None, pwd: Optional[str] = None, path: OPLike = None, folder: OPLike = None, name: Optional[str] = None, verbose: bool = True, suffix: str = ".enc", inplace: bool = False) -> "PathExtended":
760
783
  slf = self.expanduser().resolve()
761
784
  path = self._resolve_path(folder=folder, name=name, path=path, default_name=slf.name.replace(suffix, "") if suffix in slf.name else "decrypted_" + slf.name)
762
785
  path.write_bytes(decrypt(token=slf.read_bytes(), key=key, pwd=pwd))
763
- return self._return(path, operation="delete", verbose=verbose, msg=f"🔓🔑 DECRYPTED: {repr(slf)} ==> {repr(path)}.", inplace=inplace)
786
+ msg = f"🔓🔑 DECRYPTED: {repr(slf)} ==> {repr(path)}."
787
+ ret = PathExtended(path)
788
+ delayed_msg = ""
789
+ if inplace:
790
+ self.delete(sure=True, verbose=False)
791
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
792
+ if verbose:
793
+ try:
794
+ print(msg)
795
+ except UnicodeEncodeError:
796
+ print("P._return warning: UnicodeEncodeError, could not print message.")
797
+ if verbose and delayed_msg != "":
798
+ try:
799
+ print(delayed_msg)
800
+ except UnicodeEncodeError:
801
+ print("P._return warning: UnicodeEncodeError, could not print message.")
802
+ return ret
764
803
 
765
804
  def zip_n_encrypt(self, key: Optional[bytes] = None, pwd: Optional[str] = None, inplace: bool = False, verbose: bool = True, orig: bool = False, content: bool = False) -> "PathExtended":
766
805
  return self.zip(inplace=inplace, verbose=verbose, content=content).encrypt(key=key, pwd=pwd, verbose=verbose, inplace=True) if not orig else self
@@ -3,7 +3,7 @@
3
3
  import psutil
4
4
  from tqdm import tqdm
5
5
  from pytz import timezone
6
- from machineconfig.utils.utils import display_options
6
+ from machineconfig.utils.options import display_options
7
7
  from typing import Optional, Any
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
@@ -0,0 +1,27 @@
1
+ """
2
+ Utils
3
+ """
4
+
5
+ import machineconfig
6
+ import platform
7
+ from pathlib import Path
8
+
9
+ LIBRARY_ROOT = Path(machineconfig.__file__).resolve().parent
10
+ REPO_ROOT = LIBRARY_ROOT.parent.parent
11
+ PROGRAM_PATH = Path(Path.home().joinpath("tmp_results", "shells", "python_return_command").__str__() + (".ps1" if platform.system() == "Windows" else ".sh"))
12
+ CONFIG_PATH = Path.home().joinpath(".config/machineconfig")
13
+ DEFAULTS_PATH = Path.home().joinpath("dotfiles/machineconfig/defaults.ini")
14
+
15
+ INSTALL_VERSION_ROOT = CONFIG_PATH.joinpath("cli_tools_installers/versions")
16
+ INSTALL_TMP_DIR = Path.home().joinpath("tmp_results", "tmp_installers")
17
+
18
+ # LINUX_INSTALL_PATH = '/usr/local/bin'
19
+ # LINUX_INSTALL_PATH = '~/.local/bin'
20
+ LINUX_INSTALL_PATH = Path.home().joinpath(".local/bin").__str__()
21
+ WINDOWS_INSTALL_PATH = Path.home().joinpath("AppData/Local/Microsoft/WindowsApps").__str__()
22
+
23
+
24
+ if __name__ == "__main__":
25
+ # import typer
26
+ # typer.run(check_tool_exists)
27
+ pass