machineconfig 2.1__py3-none-any.whl → 2.2__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 (89) hide show
  1. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +2 -1
  2. machineconfig/cluster/templates/utils.py +0 -35
  3. machineconfig/jobs/python/check_installations.py +1 -1
  4. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  5. machineconfig/jobs/python_generic_installers/config.json +1 -1
  6. machineconfig/profile/create.py +10 -5
  7. machineconfig/profile/create_hardlinks.py +3 -1
  8. machineconfig/profile/shell.py +8 -7
  9. machineconfig/scripts/__init__.py +0 -2
  10. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  11. machineconfig/scripts/linux/devops +6 -4
  12. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  13. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  14. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  15. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  16. machineconfig/scripts/python/ai/generate_files.py +14 -15
  17. machineconfig/scripts/python/ai/mcinit.py +8 -5
  18. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  19. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  20. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  21. machineconfig/scripts/python/cloud_copy.py +22 -13
  22. machineconfig/scripts/python/cloud_mount.py +35 -23
  23. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  24. machineconfig/scripts/python/cloud_sync.py +4 -4
  25. machineconfig/scripts/python/croshell.py +37 -28
  26. machineconfig/scripts/python/devops.py +45 -27
  27. machineconfig/scripts/python/devops_add_identity.py +15 -25
  28. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  29. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  30. machineconfig/scripts/python/devops_devapps_install.py +25 -20
  31. machineconfig/scripts/python/devops_update_repos.py +142 -57
  32. machineconfig/scripts/python/dotfile.py +16 -14
  33. machineconfig/scripts/python/fire_agents.py +24 -17
  34. machineconfig/scripts/python/fire_jobs.py +91 -55
  35. machineconfig/scripts/python/ftpx.py +24 -14
  36. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  37. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  38. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  39. machineconfig/scripts/python/helpers/helpers4.py +44 -30
  40. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  41. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  42. machineconfig/scripts/python/mount_nfs.py +8 -15
  43. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  44. machineconfig/scripts/python/mount_ssh.py +8 -6
  45. machineconfig/scripts/python/repos.py +215 -57
  46. machineconfig/scripts/python/snapshot.py +0 -1
  47. machineconfig/scripts/python/start_slidev.py +10 -5
  48. machineconfig/scripts/python/start_terminals.py +22 -16
  49. machineconfig/scripts/python/viewer_template.py +0 -1
  50. machineconfig/scripts/python/wifi_conn.py +49 -75
  51. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  52. machineconfig/settings/lf/linux/lfrc +1 -0
  53. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  54. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  55. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  56. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  57. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  58. machineconfig/utils/code.py +3 -3
  59. machineconfig/utils/installer.py +2 -2
  60. machineconfig/utils/installer_utils/installer_abc.py +3 -4
  61. machineconfig/utils/installer_utils/installer_class.py +6 -4
  62. machineconfig/utils/links.py +103 -33
  63. machineconfig/utils/notifications.py +52 -38
  64. machineconfig/utils/options.py +16 -23
  65. machineconfig/utils/path_reduced.py +239 -205
  66. machineconfig/utils/procs.py +1 -1
  67. machineconfig/utils/source_of_truth.py +27 -0
  68. machineconfig/utils/ssh.py +9 -29
  69. machineconfig/utils/terminal.py +4 -2
  70. machineconfig/utils/upgrade_packages.py +91 -0
  71. machineconfig/utils/utils2.py +1 -2
  72. machineconfig/utils/utils5.py +23 -11
  73. machineconfig/utils/ve.py +4 -1
  74. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/METADATA +13 -13
  75. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/RECORD +78 -86
  76. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  77. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  78. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  79. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  80. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  81. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  82. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  84. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  85. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  86. machineconfig/utils/utils.py +0 -97
  87. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  88. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
  89. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,6 @@
1
+
1
2
  from machineconfig.utils.utils2 import randstr
3
+
2
4
  from datetime import datetime
3
5
  import time
4
6
  from pathlib import Path
@@ -6,8 +8,6 @@ import sys
6
8
  import subprocess
7
9
  from platform import system
8
10
  from typing import Any, Optional, Union, Callable, TypeAlias, Literal
9
- import os
10
- # import warnings
11
11
 
12
12
 
13
13
  OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
@@ -18,16 +18,13 @@ SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
18
18
 
19
19
  def pwd2key(password: str, salt: Optional[bytes] = None, iterations: int = 10) -> bytes: # Derive a secret key from a given password and salt"""
20
20
  import base64
21
-
22
21
  if salt is None:
23
22
  import hashlib
24
-
25
23
  m = hashlib.sha256()
26
24
  m.update(password.encode(encoding="utf-8"))
27
25
  return base64.urlsafe_b64encode(s=m.digest()) # make url-safe bytes required by Ferent.
28
26
  from cryptography.hazmat.primitives import hashes
29
27
  from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
30
-
31
28
  return base64.urlsafe_b64encode(PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=iterations, backend=None).derive(password.encode()))
32
29
 
33
30
 
@@ -101,129 +98,6 @@ def timestamp(fmt: Optional[str] = None, name: Optional[str] = None) -> str:
101
98
  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
99
 
103
100
 
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
101
  class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
228
102
  # ============= Path management ==================
229
103
  """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 +201,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
327
201
  dest_path.write_bytes(response.content)
328
202
  return dest_path
329
203
 
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
204
  def append(self, name: str = "", index: bool = False, suffix: Optional[str] = None, verbose: bool = True, **kwargs: Any) -> "PathExtended":
364
205
  """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
206
  if index:
@@ -368,10 +209,59 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
368
209
  full_name = name or ("_" + str(timestamp()))
369
210
  full_suffix = suffix or "".join(("bruh" + self).suffixes)
370
211
  subpath = self.name.split(".")[0] + full_name + full_suffix
371
- return self._return(self.parent.joinpath(subpath), operation="rename", verbose=verbose, **kwargs)
212
+ dest = self.parent.joinpath(subpath)
213
+ res = PathExtended(dest)
214
+ inplace = bool(kwargs.get("inplace", False))
215
+ overwrite = bool(kwargs.get("overwrite", False))
216
+ orig = bool(kwargs.get("orig", False))
217
+ strict = bool(kwargs.get("strict", True))
218
+ if inplace:
219
+ assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
220
+ if overwrite and res.exists():
221
+ res.delete(sure=True, verbose=verbose)
222
+ if not overwrite and res.exists():
223
+ if strict:
224
+ raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
225
+ else:
226
+ if verbose:
227
+ try:
228
+ print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
229
+ except UnicodeEncodeError:
230
+ print("P._return warning: UnicodeEncodeError, could not print message.")
231
+ return self if orig else res
232
+ self.rename(res)
233
+ if verbose:
234
+ try:
235
+ print(f"RENAMED {repr(self)} ➡️ {repr(res)}")
236
+ except UnicodeEncodeError:
237
+ print("P._return warning: UnicodeEncodeError, could not print message.")
238
+ return self if orig else res
372
239
 
373
240
  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)
241
+ res = PathExtended(self.parent / name)
242
+ orig = bool(kwargs.get("orig", False))
243
+ strict = bool(kwargs.get("strict", True))
244
+ if inplace:
245
+ assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
246
+ if overwrite and res.exists():
247
+ res.delete(sure=True, verbose=verbose)
248
+ if not overwrite and res.exists():
249
+ if strict:
250
+ raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
251
+ else:
252
+ if verbose:
253
+ try:
254
+ print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
255
+ except UnicodeEncodeError:
256
+ print("P._return warning: UnicodeEncodeError, could not print message.")
257
+ return self if orig else res
258
+ self.rename(res)
259
+ if verbose:
260
+ try:
261
+ print(f"RENAMED {repr(self)} ➡️ {repr(res)}")
262
+ except UnicodeEncodeError:
263
+ print("P._return warning: UnicodeEncodeError, could not print message.")
264
+ return self if orig else res
375
265
 
376
266
  def __deepcopy__(self, *args: Any, **kwargs: Any) -> "PathExtended":
377
267
  _ = args, kwargs
@@ -391,14 +281,14 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
391
281
  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
282
 
393
283
  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.
284
+ return PathExtended(self.expanduser().absolute().relative_to(Path.home())) # very similat to collapseuser but without "~" being added so its consistent with rel2cwd.
395
285
 
396
286
  def collapseuser(self, strict: bool = True, placeholder: str = "~") -> "PathExtended": # opposite of `expanduser` resolve is crucial to fix Windows cases insensitivty problem.
397
287
  if strict:
398
288
  assert str(self.expanduser().absolute().resolve()).startswith(str(PathExtended.home())), ValueError(f"`{PathExtended.home()}` is not in the subpath of `{self}`")
399
289
  if str(self).startswith(placeholder) or PathExtended.home().as_posix() not in self.resolve().as_posix():
400
290
  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.
291
+ return PathExtended(placeholder) / (self.expanduser().absolute().resolve(strict=strict) - PathExtended.home()) # resolve also solves the problem of Windows case insensitivty.
402
292
 
403
293
  def __getitem__(self, slici: Union[int, list[int], slice]):
404
294
  if isinstance(slici, list):
@@ -458,27 +348,9 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
458
348
  tmp = 1024**3
459
349
  return round(number=total_size / tmp, ndigits=1)
460
350
 
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
351
  # ================================ String Nature management ====================================
480
352
  def clickable(self) -> "PathExtended":
481
- return self._return(res=PathExtended(self.expanduser().resolve().as_uri()), operation="Whack")
353
+ return PathExtended(self.expanduser().resolve().as_uri())
482
354
 
483
355
  def as_url_str(self) -> "str":
484
356
  return self.as_posix().replace("https:/", "https://").replace("http:/", "http://")
@@ -522,13 +394,17 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
522
394
 
523
395
  if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
524
396
  import win32com.shell.shell
525
-
526
397
  _proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
527
398
  # TODO update PATH for this to take effect immediately.
528
399
  time.sleep(1) # wait=True equivalent
529
400
  else:
530
401
  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)}")
402
+ if verbose:
403
+ try:
404
+ print(f"LINKED {repr(self)} ➡️ {repr(target_obj)}")
405
+ except UnicodeEncodeError:
406
+ print("P._return warning: UnicodeEncodeError, could not print message.")
407
+ return self if orig else target_obj
532
408
 
533
409
  def resolve(self, strict: bool = False):
534
410
  try:
@@ -567,7 +443,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
567
443
  if ".zip" in str(slf) and compressed: # the root (self) is itself a zip archive (as opposed to some search results are zip archives)
568
444
  import zipfile
569
445
  import fnmatch
570
-
571
446
  root = slf.as_zip_path()
572
447
  if not r:
573
448
  raw = list(root.iterdir())
@@ -587,7 +462,9 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
587
462
  else:
588
463
  raw = glob(str(slf.joinpath(pattern))) # glob ignroes dot and hidden files
589
464
  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)]
465
+ filters_notin = [
466
+ 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)
467
+ ]
591
468
  from functools import reduce
592
469
 
593
470
  # haha = List(filters_notin).reduce(func=lambda x, y: x + y)
@@ -644,14 +521,40 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
644
521
  if arcname_obj.name != slf.name:
645
522
  arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
646
523
  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)
524
+ import zipfile
525
+
526
+ op_zip = str(path_resolved + ".zip" if path_resolved.suffix != ".zip" else path_resolved)
527
+ with zipfile.ZipFile(op_zip, mode=mode) as jungle_zip:
528
+ jungle_zip.write(filename=str(slf), arcname=str(arcname_obj), compress_type=zipfile.ZIP_DEFLATED, **kwargs)
529
+ path_resolved = PathExtended(op_zip)
648
530
  else:
531
+ import shutil
532
+
649
533
  if content:
650
534
  root_dir, base_dir = slf, "."
651
535
  else:
652
536
  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)}")
537
+ base_name = str(path_resolved)[:-4] if str(path_resolved).endswith(".zip") else str(path_resolved)
538
+ op_zip = shutil.make_archive(base_name=base_name, format="zip", root_dir=str(root_dir), base_dir=str(base_dir), verbose=False, **kwargs)
539
+ path_resolved = PathExtended(op_zip)
540
+ msg = f"ZIPPED {repr(slf)} ==> {repr(path)}"
541
+ res_out = PathExtended(path_resolved)
542
+ ret = self if orig else res_out
543
+ delayed_msg = ""
544
+ if inplace:
545
+ self.delete(sure=True, verbose=False)
546
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
547
+ if verbose:
548
+ try:
549
+ print(msg)
550
+ except UnicodeEncodeError:
551
+ print("P._return warning: UnicodeEncodeError, could not print message.")
552
+ if verbose and delayed_msg != "":
553
+ try:
554
+ print(delayed_msg)
555
+ except UnicodeEncodeError:
556
+ print("P._return warning: UnicodeEncodeError, could not print message.")
557
+ return ret
655
558
 
656
559
  def unzip(
657
560
  self,
@@ -706,29 +609,126 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
706
609
  # List().apply(lambda item: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True))
707
610
  for item in mylist:
708
611
  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)}")
612
+ import zipfile
613
+
614
+ target_name = None if name is None else PathExtended(name).as_posix()
615
+ with zipfile.ZipFile(str(zipfile__), "r") as zipObj:
616
+ if target_name is None:
617
+ zipObj.extractall(str(folder))
618
+ result = Path(str(folder))
619
+ else:
620
+ zipObj.extract(member=str(target_name), path=str(folder))
621
+ result = Path(str(folder)) / target_name
622
+ res_path = PathExtended(result)
623
+ msg = f"UNZIPPED {repr(zipfile__)} ==> {repr(result)}"
624
+ ret = self if orig else PathExtended(res_path)
625
+ delayed_msg = ""
626
+ if inplace:
627
+ self.delete(sure=True, verbose=False)
628
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
629
+ if verbose:
630
+ try:
631
+ print(msg)
632
+ except UnicodeEncodeError:
633
+ print("P._return warning: UnicodeEncodeError, could not print message.")
634
+ if verbose and delayed_msg != "":
635
+ try:
636
+ print(delayed_msg)
637
+ except UnicodeEncodeError:
638
+ print("P._return warning: UnicodeEncodeError, could not print message.")
639
+ return ret
712
640
 
713
641
  def untar(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
714
642
  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)}")
643
+ import tarfile
644
+
645
+ with tarfile.open(str(self.expanduser().resolve()), "r") as tf:
646
+ tf.extractall(path=str(op_path))
647
+ msg = f"UNTARRED {repr(self)} ==> {repr(op_path)}"
648
+ ret = self if orig else PathExtended(op_path)
649
+ delayed_msg = ""
650
+ if inplace:
651
+ self.delete(sure=True, verbose=False)
652
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
653
+ if verbose:
654
+ try:
655
+ print(msg)
656
+ except UnicodeEncodeError:
657
+ print("P._return warning: UnicodeEncodeError, could not print message.")
658
+ if verbose and delayed_msg != "":
659
+ try:
660
+ print(delayed_msg)
661
+ except UnicodeEncodeError:
662
+ print("P._return warning: UnicodeEncodeError, could not print message.")
663
+ return ret
717
664
 
718
665
  def ungz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
719
666
  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)}")
667
+ import gzip
668
+ PathExtended(str(op_path)).write_bytes(gzip.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
669
+ msg = f"UNGZED {repr(self)} ==> {repr(op_path)}"
670
+ ret = self if orig else PathExtended(op_path)
671
+ delayed_msg = ""
672
+ if inplace:
673
+ self.delete(sure=True, verbose=False)
674
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
675
+ if verbose:
676
+ try:
677
+ print(msg)
678
+ except UnicodeEncodeError:
679
+ print("P._return warning: UnicodeEncodeError, could not print message.")
680
+ if verbose and delayed_msg != "":
681
+ try:
682
+ print(delayed_msg)
683
+ except UnicodeEncodeError:
684
+ print("P._return warning: UnicodeEncodeError, could not print message.")
685
+ return ret
722
686
 
723
687
  def unxz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
724
688
  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)}")
689
+ import lzma
690
+
691
+ PathExtended(str(op_path)).write_bytes(lzma.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
692
+ msg = f"UNXZED {repr(self)} ==> {repr(op_path)}"
693
+ ret = self if orig else PathExtended(op_path)
694
+ delayed_msg = ""
695
+ if inplace:
696
+ self.delete(sure=True, verbose=False)
697
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
698
+ if verbose:
699
+ try:
700
+ print(msg)
701
+ except UnicodeEncodeError:
702
+ print("P._return warning: UnicodeEncodeError, could not print message.")
703
+ if verbose and delayed_msg != "":
704
+ try:
705
+ print(delayed_msg)
706
+ except UnicodeEncodeError:
707
+ print("P._return warning: UnicodeEncodeError, could not print message.")
708
+ return ret
727
709
 
728
710
  def unbz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
729
711
  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)}")
712
+ import bz2
713
+
714
+ PathExtended(str(op_path)).write_bytes(bz2.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
715
+ msg = f"UNBZED {repr(self)} ==> {repr(op_path)}"
716
+ ret = self if orig else PathExtended(op_path)
717
+ delayed_msg = ""
718
+ if inplace:
719
+ self.delete(sure=True, verbose=False)
720
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
721
+ if verbose:
722
+ try:
723
+ print(msg)
724
+ except UnicodeEncodeError:
725
+ print("P._return warning: UnicodeEncodeError, could not print message.")
726
+ if verbose and delayed_msg != "":
727
+ try:
728
+ print(delayed_msg)
729
+ except UnicodeEncodeError:
730
+ print("P._return warning: UnicodeEncodeError, could not print message.")
731
+ return ret
732
732
 
733
733
  def decompress(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
734
734
  if ".tar.gz" in str(self) or ".tgz" in str(self):
@@ -748,19 +748,53 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
748
748
  res = self
749
749
  return res
750
750
 
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":
751
+ def encrypt(
752
+ 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
753
+ ) -> "PathExtended":
752
754
  # 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
755
  slf = self.expanduser().resolve()
754
756
  path = self._resolve_path(folder, name, path, slf.name + suffix)
755
757
  assert slf.is_file(), f"Cannot encrypt a directory. You might want to try `zip_n_encrypt`. {self}"
756
758
  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)}.")
759
+ msg = f"🔒🔑 ENCRYPTED: {repr(slf)} ==> {repr(path)}."
760
+ ret = self if orig else PathExtended(path)
761
+ delayed_msg = ""
762
+ if inplace:
763
+ self.delete(sure=True, verbose=False)
764
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
765
+ if verbose:
766
+ try:
767
+ print(msg)
768
+ except UnicodeEncodeError:
769
+ print("P._return warning: UnicodeEncodeError, could not print message.")
770
+ if verbose and delayed_msg != "":
771
+ try:
772
+ print(delayed_msg)
773
+ except UnicodeEncodeError:
774
+ print("P._return warning: UnicodeEncodeError, could not print message.")
775
+ return ret
758
776
 
759
777
  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
778
  slf = self.expanduser().resolve()
761
779
  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
780
  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)
781
+ msg = f"🔓🔑 DECRYPTED: {repr(slf)} ==> {repr(path)}."
782
+ ret = PathExtended(path)
783
+ delayed_msg = ""
784
+ if inplace:
785
+ self.delete(sure=True, verbose=False)
786
+ delayed_msg = f"DELETED 🗑️❌ {repr(self)}."
787
+ if verbose:
788
+ try:
789
+ print(msg)
790
+ except UnicodeEncodeError:
791
+ print("P._return warning: UnicodeEncodeError, could not print message.")
792
+ if verbose and delayed_msg != "":
793
+ try:
794
+ print(delayed_msg)
795
+ except UnicodeEncodeError:
796
+ print("P._return warning: UnicodeEncodeError, could not print message.")
797
+ return ret
764
798
 
765
799
  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
800
  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