machineconfig 1.97__py3-none-any.whl → 2.0__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 (166) hide show
  1. machineconfig/cluster/cloud_manager.py +22 -26
  2. machineconfig/cluster/data_transfer.py +2 -2
  3. machineconfig/cluster/distribute.py +0 -2
  4. machineconfig/cluster/file_manager.py +4 -4
  5. machineconfig/cluster/job_params.py +1 -1
  6. machineconfig/cluster/loader_runner.py +8 -8
  7. machineconfig/cluster/remote_machine.py +4 -4
  8. machineconfig/cluster/script_execution.py +2 -2
  9. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +1 -1
  10. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +23 -23
  11. machineconfig/cluster/sessions_managers/wt_local.py +78 -76
  12. machineconfig/cluster/sessions_managers/wt_local_manager.py +91 -91
  13. machineconfig/cluster/sessions_managers/wt_remote.py +39 -39
  14. machineconfig/cluster/sessions_managers/wt_remote_manager.py +94 -91
  15. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +56 -54
  16. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +49 -49
  17. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +18 -18
  18. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +42 -42
  19. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +36 -36
  20. machineconfig/cluster/sessions_managers/zellij_local.py +43 -46
  21. machineconfig/cluster/sessions_managers/zellij_local_manager.py +139 -120
  22. machineconfig/cluster/sessions_managers/zellij_remote.py +35 -35
  23. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +33 -33
  24. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +15 -15
  25. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +25 -26
  26. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +49 -49
  27. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +5 -5
  28. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +15 -15
  29. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +11 -11
  30. machineconfig/cluster/templates/utils.py +3 -3
  31. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  32. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  33. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  34. machineconfig/jobs/python/check_installations.py +8 -9
  35. machineconfig/jobs/python/python_cargo_build_share.py +2 -2
  36. machineconfig/jobs/python/vscode/link_ve.py +7 -7
  37. machineconfig/jobs/python/vscode/select_interpreter.py +7 -7
  38. machineconfig/jobs/python/vscode/sync_code.py +5 -5
  39. machineconfig/jobs/python_custom_installers/archive/ngrok.py +2 -2
  40. machineconfig/jobs/python_custom_installers/dev/aider.py +3 -3
  41. machineconfig/jobs/python_custom_installers/dev/alacritty.py +3 -3
  42. machineconfig/jobs/python_custom_installers/dev/brave.py +3 -3
  43. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +5 -5
  44. machineconfig/jobs/python_custom_installers/dev/code.py +3 -3
  45. machineconfig/jobs/python_custom_installers/dev/cursor.py +9 -9
  46. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +4 -4
  47. machineconfig/jobs/python_custom_installers/dev/espanso.py +4 -4
  48. machineconfig/jobs/python_custom_installers/dev/goes.py +4 -4
  49. machineconfig/jobs/python_custom_installers/dev/lvim.py +4 -4
  50. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +3 -3
  51. machineconfig/jobs/python_custom_installers/dev/redis.py +3 -3
  52. machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -3
  53. machineconfig/jobs/python_custom_installers/dev/winget.py +27 -27
  54. machineconfig/jobs/python_custom_installers/docker.py +3 -3
  55. machineconfig/jobs/python_custom_installers/gh.py +7 -7
  56. machineconfig/jobs/python_custom_installers/hx.py +1 -1
  57. machineconfig/jobs/python_custom_installers/warp-cli.py +3 -3
  58. machineconfig/jobs/python_generic_installers/config.json +412 -389
  59. machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
  60. machineconfig/logger.py +50 -0
  61. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  62. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  63. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  64. machineconfig/profile/create.py +23 -16
  65. machineconfig/profile/create_hardlinks.py +8 -8
  66. machineconfig/profile/shell.py +41 -37
  67. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  68. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  69. machineconfig/scripts/linux/devops +2 -2
  70. machineconfig/scripts/linux/fire +1 -0
  71. machineconfig/scripts/linux/fire_agents +0 -1
  72. machineconfig/scripts/linux/mcinit +1 -1
  73. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  74. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  75. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  76. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  77. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  78. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  79. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  80. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  81. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  82. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  83. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  84. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
  85. machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
  86. machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
  87. machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
  88. machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
  89. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
  90. machineconfig/scripts/python/ai/mcinit.py +103 -0
  91. machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
  92. machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
  93. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +47 -0
  94. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  95. machineconfig/scripts/python/archive/tmate_start.py +3 -3
  96. machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
  97. machineconfig/scripts/python/cloud_copy.py +19 -18
  98. machineconfig/scripts/python/cloud_mount.py +9 -7
  99. machineconfig/scripts/python/cloud_repo_sync.py +11 -11
  100. machineconfig/scripts/python/cloud_sync.py +1 -1
  101. machineconfig/scripts/python/croshell.py +14 -14
  102. machineconfig/scripts/python/devops.py +6 -6
  103. machineconfig/scripts/python/devops_add_identity.py +8 -6
  104. machineconfig/scripts/python/devops_add_ssh_key.py +18 -18
  105. machineconfig/scripts/python/devops_backup_retrieve.py +13 -13
  106. machineconfig/scripts/python/devops_devapps_install.py +3 -3
  107. machineconfig/scripts/python/devops_update_repos.py +1 -1
  108. machineconfig/scripts/python/dotfile.py +2 -2
  109. machineconfig/scripts/python/fire_agents.py +183 -41
  110. machineconfig/scripts/python/fire_jobs.py +17 -17
  111. machineconfig/scripts/python/ftpx.py +2 -2
  112. machineconfig/scripts/python/gh_models.py +94 -94
  113. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  114. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  115. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  116. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  117. machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
  118. machineconfig/scripts/python/helpers/helpers2.py +1 -1
  119. machineconfig/scripts/python/helpers/helpers4.py +8 -6
  120. machineconfig/scripts/python/helpers/helpers5.py +7 -7
  121. machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
  122. machineconfig/scripts/python/mount_nfs.py +3 -2
  123. machineconfig/scripts/python/mount_nw_drive.py +4 -4
  124. machineconfig/scripts/python/mount_ssh.py +3 -2
  125. machineconfig/scripts/python/repos.py +8 -8
  126. machineconfig/scripts/python/scheduler.py +1 -1
  127. machineconfig/scripts/python/start_slidev.py +8 -7
  128. machineconfig/scripts/python/start_terminals.py +1 -1
  129. machineconfig/scripts/python/viewer.py +40 -40
  130. machineconfig/scripts/python/wifi_conn.py +65 -66
  131. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  132. machineconfig/scripts/windows/mcinit.ps1 +1 -1
  133. machineconfig/settings/linters/.ruff.toml +2 -2
  134. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
  135. machineconfig/settings/shells/wt/settings.json +8 -8
  136. machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
  137. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +10 -7
  138. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +9 -7
  139. machineconfig/utils/ai/browser_user_wrapper.py +5 -5
  140. machineconfig/utils/ai/generate_file_checklist.py +11 -12
  141. machineconfig/utils/ai/url2md.py +1 -1
  142. machineconfig/utils/cloud/onedrive/setup_oauth.py +4 -4
  143. machineconfig/utils/cloud/onedrive/transaction.py +129 -129
  144. machineconfig/utils/code.py +13 -6
  145. machineconfig/utils/installer.py +51 -53
  146. machineconfig/utils/installer_utils/installer_abc.py +21 -10
  147. machineconfig/utils/installer_utils/installer_class.py +42 -16
  148. machineconfig/utils/io_save.py +3 -15
  149. machineconfig/utils/options.py +10 -3
  150. machineconfig/utils/path.py +5 -0
  151. machineconfig/utils/path_reduced.py +201 -149
  152. machineconfig/utils/procs.py +23 -23
  153. machineconfig/utils/scheduling.py +11 -12
  154. machineconfig/utils/ssh.py +270 -0
  155. machineconfig/utils/terminal.py +180 -0
  156. machineconfig/utils/utils.py +1 -2
  157. machineconfig/utils/utils2.py +43 -0
  158. machineconfig/utils/utils5.py +163 -34
  159. machineconfig/utils/ve.py +2 -2
  160. {machineconfig-1.97.dist-info → machineconfig-2.0.dist-info}/METADATA +13 -8
  161. {machineconfig-1.97.dist-info → machineconfig-2.0.dist-info}/RECORD +163 -149
  162. machineconfig/cluster/self_ssh.py +0 -57
  163. machineconfig/scripts/python/ai/init.py +0 -56
  164. machineconfig/scripts/python/ai/rules/python/dev.md +0 -31
  165. {machineconfig-1.97.dist-info → machineconfig-2.0.dist-info}/WHEEL +0 -0
  166. {machineconfig-1.97.dist-info → machineconfig-2.0.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,14 @@
1
1
 
2
2
 
3
-
4
-
5
- from crocodile.core import List, timestamp, randstr, install_n_import, validate_name
6
- from crocodile.file_management_helpers.file1 import encrypt, decrypt
7
- from crocodile.file_management_helpers.file2 import Compression
8
- from crocodile.file_management_helpers.file3 import Read
9
-
3
+ from machineconfig.utils.utils2 import randstr
10
4
  from datetime import datetime
5
+ import time
11
6
  from pathlib import Path
12
7
  import sys
13
8
  import subprocess
9
+ from platform import system
14
10
  from typing import Any, Optional, Union, Callable, TypeAlias, Literal
11
+ import os
15
12
 
16
13
 
17
14
  OPLike: TypeAlias = Union[str, 'P', Path, None]
@@ -20,6 +17,68 @@ FILE_MODE: TypeAlias = Literal['r', 'w', 'x', 'a']
20
17
  SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
21
18
 
22
19
 
20
+ def pwd2key(password: str, salt: Optional[bytes] = None, iterations: int = 10) -> bytes: # Derive a secret key from a given password and salt"""
21
+ import base64
22
+ if salt is None:
23
+ import hashlib
24
+ m = hashlib.sha256()
25
+ m.update(password.encode(encoding="utf-8"))
26
+ return base64.urlsafe_b64encode(s=m.digest()) # make url-safe bytes required by Ferent.
27
+ from cryptography.hazmat.primitives import hashes
28
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
29
+ return base64.urlsafe_b64encode(PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=iterations, backend=None).derive(password.encode()))
30
+ def encrypt(msg: bytes, key: Optional[bytes] = None, pwd: Optional[str] = None, salted: bool = True, iteration: Optional[int] = None, gen_key: bool = False) -> bytes:
31
+ import base64
32
+ from cryptography.fernet import Fernet
33
+ salt, iteration = None, None
34
+ if pwd is not None: # generate it from password
35
+ assert (key is None) and (type(pwd) is str), "❌ You can either pass key or pwd, or none of them, but not both."
36
+ import secrets
37
+ iteration = iteration or secrets.randbelow(exclusive_upper_bound=1_000_000)
38
+ salt = secrets.token_bytes(nbytes=16) if salted else None
39
+ key_resolved = pwd2key(password=pwd, salt=salt, iterations=iteration)
40
+ elif key is None:
41
+ if gen_key:
42
+ key_resolved = Fernet.generate_key()
43
+ Path.home().joinpath('dotfiles/creds/data/encrypted_files_key.bytes').write_bytes(key_resolved)
44
+ else:
45
+ try:
46
+ key_resolved = Path.home().joinpath("dotfiles/creds/data/encrypted_files_key.bytes").read_bytes()
47
+ print(f"⚠️ Using key from: {Path.home().joinpath('dotfiles/creds/data/encrypted_files_key.bytes')}")
48
+ except FileNotFoundError as err:
49
+ print("\n" * 3, "~" * 50, """Consider Loading up your dotfiles or pass `gen_key=True` to make and save one.""", "~" * 50, "\n" * 3)
50
+ raise FileNotFoundError(err) from err
51
+ elif isinstance(key, (str, P, Path)): key_resolved = Path(key).read_bytes() # a path to a key file was passed, read it:
52
+ elif type(key) is bytes: key_resolved = key # key passed explicitly
53
+ else: raise TypeError("❌ Key must be either a path, bytes object or None.")
54
+ code = Fernet(key=key_resolved).encrypt(msg)
55
+ if pwd is not None and salt is not None and iteration is not None: return base64.urlsafe_b64encode(b'%b%b%b' % (salt, iteration.to_bytes(4, 'big'), base64.urlsafe_b64decode(code)))
56
+ return code
57
+ def decrypt(token: bytes, key: Optional[bytes] = None, pwd: Optional[str] = None, salted: bool = True) -> bytes:
58
+ import base64
59
+ if pwd is not None:
60
+ assert key is None, "❌ You can either pass key or pwd, or none of them, but not both."
61
+ if salted:
62
+ decoded = base64.urlsafe_b64decode(token)
63
+ salt, iterations, token = decoded[:16], decoded[16:20], base64.urlsafe_b64encode(decoded[20:])
64
+ key_resolved = pwd2key(password=pwd, salt=salt, iterations=int.from_bytes(bytes=iterations, byteorder='big'))
65
+ else: key_resolved = pwd2key(password=pwd) # trailing `;` prevents IPython from caching the result.
66
+ elif type(key) is bytes:
67
+ assert pwd is None, "❌ You can either pass key or pwd, or none of them, but not both."
68
+ key_resolved = key # passsed explicitly
69
+ elif key is None: key_resolved = Path.home().joinpath("dotfiles/creds/data/encrypted_files_key.bytes").read_bytes() # read from file
70
+ elif isinstance(key, (str, Path)): key_resolved = Path(key).read_bytes() # passed a path to a file containing kwy
71
+ else: raise TypeError(f"❌ Key must be either str, P, Path, bytes or None. Recieved: {type(key)}")
72
+ from cryptography.fernet import Fernet
73
+ return Fernet(key=key_resolved).decrypt(token)
74
+
75
+ def validate_name(astring: str, replace: str = '_') -> str:
76
+ import re
77
+ return re.sub(r'[^-a-zA-Z0-9_.()]+', replace, str(astring))
78
+ def timestamp(fmt: Optional[str] = None, name: Optional[str] = None) -> str:
79
+ 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.
80
+
81
+
23
82
  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):
24
83
  lines, bingo = txt_raw.split("\n"), False
25
84
  if not replace_line: # no need for line splitting
@@ -38,6 +97,76 @@ def modify_text(txt_raw: str, txt_search: str, txt_alt: Union[str, Callable[[str
38
97
  else: lines.append(txt_alt) # txt not found, add it anyway.
39
98
  return "\n".join(lines)
40
99
 
100
+
101
+ class Compression:
102
+ @staticmethod
103
+ def compress_folder(root_dir: str, op_path: str, base_dir: str, fmt: SHUTIL_FORMATS = 'zip', verbose: bool = False, **kwargs: Any) -> 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."""
104
+ 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.
105
+ import shutil
106
+ 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.
107
+ @staticmethod
108
+ def zip_file(ip_path: str, op_path: str, arcname: Optional[str]= None, password: Optional[bytes] = None, mode: FILE_MODE = "w", **kwargs: Any):
109
+ """arcname determines the directory of the file being archived inside the archive. Defaults to same as original directory except for drive.
110
+ 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."""
111
+ import zipfile
112
+ with zipfile.ZipFile(op_path, mode=mode) as jungle_zip:
113
+ if password is not None: jungle_zip.setpassword(pwd=password)
114
+ jungle_zip.write(filename=str(ip_path), arcname=str(arcname) if arcname is not None else None, compress_type=zipfile.ZIP_DEFLATED, **kwargs)
115
+ return Path(op_path)
116
+ @staticmethod
117
+ 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:
118
+ import zipfile
119
+ with zipfile.ZipFile(str(ip_path), 'r') as zipObj:
120
+ if memory:
121
+ return {name: zipObj.read(name) for name in zipObj.namelist()} if fname is None else zipObj.read(fname)
122
+ if fname is None:
123
+ zipObj.extractall(op_path, pwd=password, **kwargs)
124
+ return Path(op_path)
125
+ else:
126
+ zipObj.extract(member=str(fname), path=str(op_path), pwd=password)
127
+ return Path(op_path) / fname
128
+ @staticmethod
129
+ 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
130
+ import shutil
131
+ import gzip
132
+ with open(file, 'rb') as f_in:
133
+ with gzip.open(op_path, 'wb') as f_out: shutil.copyfileobj(f_in, f_out)
134
+ return Path(op_path)
135
+ @staticmethod
136
+ def ungz(path: str, op_path: str):
137
+ import gzip
138
+ import shutil
139
+ with gzip.open(path, 'r') as f_in, open(op_path, 'wb') as f_out: shutil.copyfileobj(f_in, f_out)
140
+ return Path(op_path)
141
+ @staticmethod
142
+ def unbz(path: str, op_path: str):
143
+ import bz2
144
+ import shutil
145
+ with bz2.BZ2File(path, 'r') as fr, open(str(op_path), 'wb') as fw: shutil.copyfileobj(fr, fw)
146
+ return Path(op_path)
147
+ @staticmethod
148
+ def xz(path: str, op_path: str):
149
+ import lzma
150
+ with lzma.open(op_path, "w") as f: f.write(Path(path).read_bytes())
151
+ @staticmethod
152
+ def unxz(ip_path: str, op_path: str):
153
+ import lzma
154
+ with lzma.open(ip_path) as file: Path(op_path).write_bytes(file.read())
155
+ @staticmethod
156
+ def tar(path: str, op_path: str):
157
+ import tarfile
158
+ with tarfile.open(op_path, "w:gz") as tar_: tar_.add(str(path), arcname=os.path.basename(path))
159
+ return Path(op_path)
160
+ @staticmethod
161
+ def untar(path: str, op_path: str, fname: Optional[str]= None, mode: Literal['r', 'w'] = 'r', **kwargs: Any):
162
+ import tarfile
163
+ with tarfile.open(str(path), mode) as file:
164
+ if fname is None: file.extractall(path=op_path, **kwargs) # extract all files in the archive
165
+ else: file.extract(fname, **kwargs)
166
+ return Path(op_path)
167
+
168
+
169
+
41
170
  class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
42
171
  # ============= Path management ==================
43
172
  """ 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.
@@ -65,7 +194,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
65
194
  slf = self.expanduser().resolve()
66
195
  if content:
67
196
  assert self.is_dir(), NotADirectoryError(f"💥 When `content` flag is set to True, path must be a directory. It is not: `{repr(self)}`")
68
- self.search("*").apply(lambda x: x.move(folder=path.parent, content=False, overwrite=overwrite))
197
+ [x.move(folder=path.parent, content=False, overwrite=overwrite) for x in self.search("*")]
69
198
  return path # contents live within this directory.
70
199
  if overwrite:
71
200
  tmp_path = slf.rename(path.parent.absolute() / randstr())
@@ -103,47 +232,6 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
103
232
  else: print(f"💥 Could NOT COPY. Not a file nor a path: {repr(slf)}.")
104
233
  return dest if not orig else self
105
234
  # ======================================= File Editing / Reading ===================================
106
- def readit(self, reader: Optional[Callable[[PLike], Any]] = None, strict: bool = True, default: Optional[Any] = None, verbose: bool = False, **kwargs: Any) -> 'Any':
107
- slf = self.expanduser().resolve()
108
- if not slf.exists():
109
- if strict: raise FileNotFoundError(f"`{slf}` is no where to be found!")
110
- else:
111
- if verbose: print(f"💥 P.readit warning: FileNotFoundError, skipping reading of file `{self}")
112
- return default
113
- if verbose: print(f"Reading {slf} ({slf.size()} MB) ...")
114
- if '.tar.gz' in str(slf) or '.tgz' in str(slf) or '.gz' in str(slf) or '.tar.bz' in str(slf) or 'tbz' in str(slf) or 'tar.xz' in str(slf) or '.zip' in str(slf):
115
- filename = slf.decompress(folder=slf.tmp(folder="tmp_unzipped"), verbose=True)
116
- if filename.is_dir():
117
- tmp_content = filename.search("*")
118
- if len(tmp_content) == 1:
119
- print(f"⚠️ Found only one file in the unzipped folder: {tmp_content[0]}")
120
- filename = tmp_content.list[0]
121
- else:
122
- if strict: raise ValueError(f"❌ Expected only one file in the unzipped folder, but found {len(tmp_content)} files.")
123
- else: print(f"⚠️ Found {len(tmp_content)} files in the unzipped folder. Using the first one: {tmp_content[0]}")
124
- filename = tmp_content.list[0]
125
- else: filename = slf
126
- try:
127
- return Read.read(filename, **kwargs) if reader is None else reader(str(filename), **kwargs)
128
- except IOError as ioe: raise IOError from ioe
129
- # DEPRECATED: append_text has been removed. Use the inline equivalent instead:
130
- # p.write_text(p.read_text() + appendix)
131
- # Returning the path (p) is preserved by write_text in this class.
132
- # Example:
133
- # p = p.write_text(p.read_text() + appendix)
134
- # def append_text(self, appendix: str) -> 'P':
135
- # self.write_text(self.read_text() + appendix)
136
- # return self
137
- # DEPRECATED: Instance method modify_text is deprecated and left commented-out to prevent new usage.
138
- # Please inline using the module-level modify_text helper:
139
- # current = p.read_text() if p.exists() else ""
140
- # updated = modify_text(current, search, alt, replace_line=..., notfound_append=..., prepend=...)
141
- # p.write_text(updated)
142
- # def modify_text(self, txt_search: str, txt_alt: str, replace_line: bool = False, notfound_append: bool = False, prepend: bool = False, encoding: str = 'utf-8'):
143
- # if not self.exists():
144
- # self.parent.mkdir(parents=True, exist_ok=True)
145
- # self.write_text(txt_search)
146
- # return self.write_text(modify_text(txt_raw=self.read_text(encoding=encoding), txt_search=txt_search, txt_alt=txt_alt, replace_line=replace_line, notfound_append=notfound_append, prepend=prepend), encoding=encoding)
147
235
  def download(self, folder: OPLike = None, name: Optional[str]= None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> 'P':
148
236
  import requests
149
237
  response = requests.get(self.as_url_str(), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
@@ -156,7 +244,8 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
156
244
  f_name = validate_name(str(P(response.history[-1].url).name if len(response.history) > 0 else P(response.url).name))
157
245
  dest_path = (P.home().joinpath("Downloads") if folder is None else P(folder)).joinpath(f_name)
158
246
  dest_path.parent.mkdir(parents=True, exist_ok=True)
159
- return dest_path.write_bytes(response.content)
247
+ dest_path.write_bytes(response.content)
248
+ return dest_path
160
249
  def _return(self, res: Union['P', '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 = "") -> 'P':
161
250
  res = P(res)
162
251
  if inplace:
@@ -180,11 +269,6 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
180
269
  try: print(__delayed_msg__)
181
270
  except UnicodeEncodeError: print("P._return warning: UnicodeEncodeError, could not print message.")
182
271
  return self if orig else res
183
- # # ================================ Path Object management ===========================================
184
- # """ Distinction between Path object and the underlying file on disk that the path may refer to. Two distinct flags are used:
185
- # `inplace`: the operation on the path object will affect the underlying file on disk if this flag is raised, otherwise the method will only alter the string.
186
- # `inliue`: the method acts on the path object itself instead of creating a new one if this flag is raised.
187
- # `orig`: whether the method returns the original path object or a new one."""
188
272
  def append(self, name: str = '', index: bool = False, suffix: Optional[str] = None, verbose: bool = True, **kwargs: Any) -> 'P':
189
273
  """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."""
190
274
  if index:
@@ -196,12 +280,6 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
196
280
  return self._return(self.parent.joinpath(subpath), operation="rename", verbose=verbose, **kwargs)
197
281
  def with_name(self, name: str, verbose: bool = True, inplace: bool = False, overwrite: bool = False, **kwargs: Any):
198
282
  return self._return(self.parent / name, verbose=verbose, operation="rename", inplace=inplace, overwrite=overwrite, **kwargs)
199
- # ============================= attributes of object ======================================
200
- # @property
201
- # def items(self) -> List[str]: return List(self.parts)
202
- # def __len__(self) -> int: return len(self.parts)
203
- # def __contains__(self, item: PLike): return P(item).as_posix() in self.as_posix()
204
- # def __iter__(self): return self.parts.__iter__()
205
283
  def __deepcopy__(self, *args: Any, **kwargs: Any) -> 'P':
206
284
  _ = args, kwargs
207
285
  return P(str(self))
@@ -213,10 +291,10 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
213
291
  def __sub__(self, other: PLike) -> 'P':
214
292
  res = P(str(self).replace(str(other), ""))
215
293
  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.
216
-
294
+
217
295
  def rel2home(self, ) -> 'P': return self._return(P(self.expanduser().absolute().relative_to(Path.home())), operation='Whack') # very similat to collapseuser but without "~" being added so its consistent with rel2cwd.
218
296
  def collapseuser(self, strict: bool = True, placeholder: str = "~") -> 'P': # opposite of `expanduser` resolve is crucial to fix Windows cases insensitivty problem.
219
- if strict: assert P.home() in self.expanduser().absolute().resolve(), ValueError(f"`{P.home()}` is not in the subpath of `{self}`")
297
+ if strict: assert str(self.expanduser().absolute().resolve()).startswith(str(P.home())), ValueError(f"`{P.home()}` is not in the subpath of `{self}`")
220
298
  if (str(self).startswith(placeholder) or P.home().as_posix() not in self.resolve().as_posix()): return self
221
299
  return self._return(res=P(placeholder) / (self.expanduser().absolute().resolve(strict=strict) - P.home()), operation='Whack') # resolve also solves the problem of Windows case insensitivty.
222
300
  def __getitem__(self, slici: Union[int, list[int], slice]):
@@ -268,7 +346,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
268
346
  case "a": tmp = self.stat().st_atime
269
347
  case "c": tmp = self.stat().st_ctime
270
348
  return datetime.fromtimestamp(tmp, **kwargs)
271
-
349
+
272
350
  # ================================ String Nature management ====================================
273
351
  def clickable(self, ) -> 'P': return self._return(res=P(self.expanduser().resolve().as_uri()), operation='Whack')
274
352
  def as_url_str(self) -> 'str': return self.as_posix().replace("https:/", "https://").replace("http:/", "http://")
@@ -293,39 +371,17 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
293
371
  elif self.is_dir(): return "📁"
294
372
  return "👻NotExist"
295
373
  return "📍Relative"
296
- def write_text(self, data: str, encoding: str = 'utf-8', newline: Optional[str] = None) -> 'P':
297
- self.parent.mkdir(parents=True, exist_ok=True)
298
- super(P, self).write_text(data, encoding=encoding, newline=newline)
299
- return self
300
- def read_text(self, encoding: Optional[str] = 'utf-8') -> str: return super(P, self).read_text(encoding=encoding)
301
- def write_bytes(self, data: bytes, overwrite: bool = False) -> 'P':
302
- slf = self.expanduser().absolute()
303
- if overwrite and slf.exists(): slf.delete(sure=True)
304
- res = super(P, slf).write_bytes(data)
305
- if res == 0: raise RuntimeError("Could not save file on disk.")
306
- return self
307
- # def touch(self, mode: int = 0o666, parents: bool = True, exist_ok: bool = True) -> 'P': # pylint: disable=W0237
308
- # """Deprecated: rely on pathlib.Path.touch at call sites.
309
- # Behavior was:
310
- # - if parents: ensure parent directories exist
311
- # - then call Path.touch(mode=mode, exist_ok=exist_ok)
312
- # - return self
313
- # Replace usages with:
314
- # p.parent.mkdir(parents=True, exist_ok=True); p.touch(mode=..., exist_ok=...)
315
- # """
316
- # if parents: self.parent.mkdir(parents=parents, exist_ok=True)
317
- # super(P, self).touch(mode=mode, exist_ok=exist_ok)
318
- # return self
319
-
320
- def symlink_to(self, target: PLike, verbose: bool = True, overwrite: bool = False, orig: bool = False, strict: bool = True): # pylint: disable=W0237
374
+ def symlink_to(self, target: PLike, verbose: bool = True, overwrite: bool = False, orig: bool = False, strict: bool = True): # type: ignore[override] # pylint: disable=W0237
321
375
  self.parent.mkdir(parents=True, exist_ok=True)
322
376
  target_obj = P(target).expanduser().resolve()
323
377
  if strict: assert target_obj.exists(), f"Target path `{target}` (aka `{target_obj}`) doesn't exist. This will create a broken link."
324
378
  if overwrite and (self.is_symlink() or self.exists()): self.delete(sure=True, verbose=verbose)
325
- from platform import system
326
- from crocodile.meta import Terminal
379
+ from machineconfig.utils.terminal import Terminal
327
380
  if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
328
- Terminal.run_as_admin(file=sys.executable, params=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"", wait=True)
381
+ import win32com.shell.shell
382
+ _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)}')\"")
383
+ # TODO update PATH for this to take effect immediately.
384
+ time.sleep(1) # wait=True equivalent
329
385
  else: super(P, self.expanduser()).symlink_to(str(target_obj))
330
386
  return self._return(target_obj, operation='Whack', inplace=False, orig=orig, verbose=verbose, msg=f"LINKED {repr(self)} ➡️ {repr(target_obj)}")
331
387
  def resolve(self, strict: bool = False):
@@ -333,7 +389,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
333
389
  except OSError: return self
334
390
  # ======================================== Folder management =======================================
335
391
  def search(self, pattern: str = '*', r: bool = False, files: bool = True, folders: bool = True, compressed: bool = False, dotfiles: bool = False, filters_total: Optional[list[Callable[[Any], bool]]] = None, not_in: Optional[list[str]] = None,
336
- exts: Optional[list[str]] = None, win_order: bool = False) -> List['P']:
392
+ exts: Optional[list[str]] = None, win_order: bool = False) -> list['P']:
337
393
  if isinstance(not_in, list):
338
394
  filters_notin = [lambda x: all([str(a_not_in) not in str(x) for a_not_in in not_in])] # type: ignore
339
395
  else: filters_notin = []
@@ -349,11 +405,13 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
349
405
  import fnmatch
350
406
  root = slf.as_zip_path()
351
407
  if not r:
352
- raw = List(root.iterdir())
408
+ raw = list(root.iterdir())
353
409
  else:
354
- raw = List(zipfile.ZipFile(str(slf)).namelist()).apply(root.joinpath)
355
- res1 = raw.filter(lambda zip_path: fnmatch.fnmatch(zip_path.at, pattern)) # type: ignore
356
- return res1.filter(lambda x: (folders or x.is_file()) and (files or x.is_dir())) # type: ignore
410
+ raw = [root.joinpath(item) for item in zipfile.ZipFile(str(slf)).namelist()]
411
+ # res1 = raw.filter(lambda zip_path: fnmatch.fnmatch(zip_path.at, pattern)) # type: ignore
412
+ res1 = [item for item in raw if fnmatch.fnmatch(item.at, pattern)]
413
+ # return res1.filter(lambda x: (folders or x.is_file()) and (files or x.is_dir()))
414
+ return [item for item in res1 if (folders or item.is_file()) and (files or item.is_dir())] # type: ignore
357
415
  elif dotfiles: raw = slf.glob(pattern) if not r else self.rglob(pattern)
358
416
  else:
359
417
  from glob import glob
@@ -363,31 +421,19 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
363
421
  raw = glob(str(slf.joinpath(pattern))) # glob ignroes dot and hidden files
364
422
  if ".zip" not in str(slf) and compressed:
365
423
  filters_notin = [P(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)]
366
- haha = List(filters_notin).reduce(func=lambda x, y: x + y)
424
+ from functools import reduce
425
+ # haha = List(filters_notin).reduce(func=lambda x, y: x + y)
426
+ haha = reduce(lambda x, y: x + y, filters_notin) if len(filters_notin) else []
367
427
  raw = raw + haha # type: ignore
368
428
  processed = []
369
429
  for item in raw:
370
430
  item_ = P(item)
371
431
  if all([afilter(item_) for afilter in filters_total]):
372
432
  processed.append(item_)
373
- if not win_order: return List(processed)
433
+ if not win_order: return list(processed)
374
434
  import re
375
435
  processed.sort(key=lambda x: [int(k) if k.isdigit() else k for k in re.split('([0-9]+)', string=x.stem)])
376
- return List(processed)
377
-
378
- # def create(self, parents: bool = True, exist_ok: bool = True, parents_only: bool = False) -> 'P':
379
- # """Deprecated. Use Path.mkdir directly at the call site:
380
- # - When creating a directory: self.mkdir(parents=True, exist_ok=True)
381
- # - When ensuring parent exists: self.parent.mkdir(parents=True, exist_ok=True)
382
- # This method used to:
383
- # target_path = self.parent if parents_only else self
384
- # target_path.mkdir(parents=parents, exist_ok=exist_ok)
385
- # return self
386
- # """
387
- # target_path = self.parent if parents_only else self
388
- # target_path.mkdir(parents=parents, exist_ok=exist_ok)
389
- # return self
390
-
436
+ return list(processed)
391
437
  @staticmethod
392
438
  def tmpdir(prefix: str = "") -> 'P':
393
439
  return P.tmp(folder=rf"tmp_dirs/{prefix + ('_' if prefix != '' else '') + randstr()}")
@@ -403,20 +449,19 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
403
449
  return base
404
450
  # ====================================== Compression & Encryption ===========================================
405
451
  def zip(self, path: OPLike = None, folder: OPLike = None, name: Optional[str]= None, arcname: Optional[str] = None, inplace: bool = False, verbose: bool = True,
406
- content: bool = False, orig: bool = False, use_7z: bool = False, pwd: Optional[str] = None, mode: FILE_MODE = 'w', **kwargs: Any) -> 'P':
452
+ content: bool = False, orig: bool = False, pwd: Optional[str] = None, mode: FILE_MODE = 'w', **kwargs: Any) -> 'P':
407
453
  path_resolved, slf = self._resolve_path(folder, name, path, self.name).expanduser().resolve(), self.expanduser().resolve()
408
- if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
409
- path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
410
- with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
454
+ # if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
455
+ # path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
456
+ # with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
457
+ arcname_obj = P(arcname or slf.name)
458
+ if arcname_obj.name != slf.name: arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
459
+ if slf.is_file():
460
+ 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)
411
461
  else:
412
- arcname_obj = P(arcname or slf.name)
413
- if arcname_obj.name != slf.name: arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
414
- if slf.is_file():
415
- 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)
416
- else:
417
- if content: root_dir, base_dir = slf, "."
418
- else: root_dir, base_dir = slf.split(at=str(arcname_obj[0]), sep=1)[0], str(arcname_obj)
419
- path_resolved = P(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
462
+ if content: root_dir, base_dir = slf, "."
463
+ else: root_dir, base_dir = slf.split(at=str(arcname_obj[0]), sep=1)[0], str(arcname_obj)
464
+ path_resolved = P(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
420
465
  return self._return(path_resolved, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"ZIPPED {repr(slf)} ==> {repr(path)}")
421
466
  def unzip(self, folder: OPLike = None, path: OPLike = None, name: Optional[str]= None, verbose: bool = True, content: bool = False, inplace: bool = False, overwrite: bool = False, orig: bool = False,
422
467
  pwd: Optional[str] = None, tmp: bool = False, pattern: Optional[str] = None, merge: bool = False) -> 'P':
@@ -428,26 +473,31 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
428
473
  tmp__ = [item for item in (".zip", ".7z", "") if item in str(slf)]
429
474
  ztype = tmp__[0]
430
475
  if ztype == "": return slf
431
- zipfile__, name__ = slf.split(at=str(List(slf.parts).filter(lambda x: ztype in x)[0]), sep=-1)
476
+ # zipfile__, name__ = slf.split(at=str(List(slf.parts).filter(lambda x: ztype in x)[0]), sep=-1)
477
+ zipfile__, name__ = slf.split(at=str(next(item for item in slf.parts if ztype in item)), sep=-1)
432
478
  name = str(name__)
433
479
  folder = (zipfile__.parent / zipfile__.stem) if folder is None else P(folder).expanduser().absolute().resolve().joinpath(zipfile__.stem)
480
+ assert isinstance(folder, P), "folder should be a P object at this point"
434
481
  folder = folder if not content else folder.parent
435
482
  if slf.suffix == ".7z":
436
- if overwrite: P(folder).delete(sure=True)
437
- result = folder
438
- import py7zr
439
- with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
440
- if pattern is not None:
441
- import re
442
- pat = re.compile(pattern)
443
- archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
444
- else: archive.extractall(path=folder)
483
+ raise NotImplementedError("I have not implemented this yet")
484
+ # if overwrite: P(folder).delete(sure=True)
485
+ # result = folder
486
+ # import py7zr
487
+ # with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
488
+ # if pattern is not None:
489
+ # import re
490
+ # pat = re.compile(pattern)
491
+ # archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
492
+ # else: archive.extractall(path=folder)
445
493
  else:
446
494
  if overwrite:
447
495
  if not content: P(folder).joinpath(name or "").delete(sure=True, verbose=True) # deletes a specific file / folder that has the same name as the zip file without extension.
448
496
  else:
449
497
  import zipfile
450
- List([x for x in zipfile.ZipFile(str(self)).namelist() if "/" not in x or (len(x.split('/')) == 2 and x.endswith("/"))]).apply(lambda item: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True))
498
+ mylist = [x for x in zipfile.ZipFile(str(self)).namelist() if "/" not in x or (len(x.split('/')) == 2 and x.endswith("/"))]
499
+ # List().apply(lambda item: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True))
500
+ for item in mylist: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True)
451
501
  result = Compression.unzip(str(zipfile__), str(folder), None if name is None else P(name).as_posix())
452
502
  assert isinstance(result, Path)
453
503
  return self._return(P(result), inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNZIPPED {repr(zipfile__)} ==> {repr(result)}")
@@ -492,7 +542,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
492
542
  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) -> 'P':
493
543
  slf = self.expanduser().resolve()
494
544
  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)
495
- path.write_bytes(data=decrypt(token=slf.read_bytes(), key=key, pwd=pwd))
545
+ path.write_bytes(decrypt(token=slf.read_bytes(), key=key, pwd=pwd))
496
546
  return self._return(path, operation="delete", verbose=verbose, msg=f"🔓🔑 DECRYPTED: {repr(slf)} ==> {repr(path)}.", inplace=inplace)
497
547
  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) -> 'P':
498
548
  return self.zip(inplace=inplace, verbose=verbose, content=content).encrypt(key=key, pwd=pwd, verbose=verbose, inplace=True) if not orig else self
@@ -502,12 +552,13 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
502
552
  if path is not None:
503
553
  path = P(self.joinpath(path).resolve() if rel2it else path).expanduser().resolve()
504
554
  assert folder is None and name is None, "If `path` is passed, `folder` and `name` cannot be passed."
555
+ assert isinstance(path, P), "path should be a P object at this point"
505
556
  assert not path.is_dir(), f"`path` passed is a directory! it must not be that. If this is meant, pass it with `folder` kwarg. `{path}`"
506
557
  return path
507
558
  name, folder = (default_name if name is None else str(name)), (self.parent if folder is None else folder) # good for edge cases of path with single part. # means same directory, just different name
508
559
  return P(self.joinpath(folder).resolve() if rel2it else folder).expanduser().resolve() / name
509
560
 
510
- def get_remote_path(self, root: Optional[str], os_specific: bool = False, rel2home: bool = True, strict: bool = True, obfuscate: bool = False) -> 'P':
561
+ def get_remote_path(self, root: Optional[str], os_specific: bool = False, rel2home: bool = True, strict: bool = True) -> 'P':
511
562
  import platform
512
563
  tmp1: str = (platform.system().lower() if os_specific else 'generic_os')
513
564
  if not rel2home: path = self
@@ -516,10 +567,10 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
516
567
  except ValueError as ve:
517
568
  if strict: raise ve
518
569
  path = self
519
- if obfuscate:
520
- from crocodile.msc.obfuscater import obfuscate as obfuscate_func
521
- name = obfuscate_func(seed=P.home().joinpath('dotfiles/creds/data/obfuscation_seed').read_text().rstrip(), data=path.name)
522
- path = path.with_name(name=name)
570
+ # if obfuscate:
571
+ # msc.obfuscater import obfuscate as obfuscate_func
572
+ # name = obfuscate_func(seed=P.home().joinpath('dotfiles/creds/data/obfuscation_seed').read_text(encoding="utf-8").rstrip(), data=path.name)
573
+ # path = path.with_name(name=name)
523
574
  if isinstance(root, str): # the following is to avoid the confusing behaviour of A.joinpath(B) if B is absolute.
524
575
  part1 = path.parts[0]
525
576
  if part1 == "/": sanitized_path = path[1:].as_posix()
@@ -528,7 +579,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
528
579
  return tmp1 / path
529
580
  def to_cloud(self, cloud: str, remotepath: OPLike = None, zip: bool = False,encrypt: bool = False, # pylint: disable=W0621, W0622
530
581
  key: Optional[bytes] = None, pwd: Optional[str] = None, rel2home: bool = False, strict: bool = True,
531
- obfuscate: bool = False,
582
+ # obfuscate: bool = False,
532
583
  share: bool = False, verbose: bool = True, os_specific: bool = False, transfers: int = 10, root: Optional[str] = "myhome") -> 'P':
533
584
  to_del = []
534
585
  localpath = self.expanduser().absolute() if not self.exists() else self
@@ -539,10 +590,10 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
539
590
  localpath = localpath.encrypt(key=key, pwd=pwd, inplace=False)
540
591
  to_del.append(localpath)
541
592
  if remotepath is None:
542
- rp = localpath.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict, obfuscate=obfuscate) # if rel2home else (P(root) / localpath if root is not None else localpath)
593
+ rp = localpath.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict) # if rel2home else (P(root) / localpath if root is not None else localpath)
543
594
  else: rp = P(remotepath)
544
595
  rclone_cmd = f"""rclone copyto '{localpath.as_posix()}' '{cloud}:{rp.as_posix()}' {'--progress' if verbose else ''} --transfers={transfers}"""
545
- from crocodile.meta import Terminal
596
+ from machineconfig.utils.terminal import Terminal
546
597
  if verbose: print(f"{'⬆️'*5} UPLOADING with `{rclone_cmd}`")
547
598
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
548
599
  res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use).capture()
@@ -559,7 +610,8 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
559
610
  raise RuntimeError(f"💥 Could not get link for {self}.")
560
611
  else:
561
612
  res.print_if_unsuccessful(desc="Cloud Storage Operation", strict_err=True, strict_returncode=True)
562
- return tmp
613
+ link_p: 'P' = P(str(tmp))
614
+ return link_p
563
615
  return self
564
616
  def from_cloud(self, cloud: str, remotepath: OPLike = None, decrypt: bool = False, unzip: bool = False, # type: ignore # pylint: disable=W0621
565
617
  key: Optional[bytes] = None, pwd: Optional[str] = None, rel2home: bool = False, os_specific: bool = False, strict: bool = True,
@@ -573,7 +625,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
573
625
  localpath += ".zip" if unzip else ""
574
626
  localpath += ".enc" if decrypt else ""
575
627
  rclone_cmd = f"""rclone copyto '{cloud}:{remotepath.as_posix()}' '{localpath.as_posix()}' {'--progress' if verbose else ''} --transfers={transfers}"""
576
- from crocodile.meta import Terminal
628
+ from machineconfig.utils.terminal import Terminal
577
629
  if verbose: print(f"{'⬇️' * 5} DOWNLOADING with `{rclone_cmd}`")
578
630
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
579
631
  res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)
@@ -597,7 +649,7 @@ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
597
649
  rclone_cmd = f"""rclone sync '{source}' '{target}' """
598
650
  rclone_cmd += f" --progress --transfers={transfers} --verbose"
599
651
  rclone_cmd += (" --delete-during" if delete else "")
600
- from crocodile.meta import Terminal
652
+ from machineconfig.utils.terminal import Terminal
601
653
  if verbose : print(rclone_cmd)
602
654
  shell_to_use = "powershell" if sys.platform == "win32" else "bash"
603
655
  res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)