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.
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +2 -1
- machineconfig/cluster/templates/utils.py +0 -35
- machineconfig/jobs/python/check_installations.py +1 -1
- machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
- machineconfig/jobs/python_generic_installers/config.json +1 -1
- machineconfig/profile/create.py +10 -5
- machineconfig/profile/create_hardlinks.py +3 -1
- machineconfig/profile/shell.py +8 -7
- machineconfig/scripts/__init__.py +0 -2
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/linux/devops +6 -4
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/generate_files.py +14 -15
- machineconfig/scripts/python/ai/mcinit.py +8 -5
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +7 -7
- machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
- machineconfig/scripts/python/cloud_copy.py +22 -13
- machineconfig/scripts/python/cloud_mount.py +35 -23
- machineconfig/scripts/python/cloud_repo_sync.py +38 -25
- machineconfig/scripts/python/cloud_sync.py +4 -4
- machineconfig/scripts/python/croshell.py +37 -28
- machineconfig/scripts/python/devops.py +45 -27
- machineconfig/scripts/python/devops_add_identity.py +15 -25
- machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
- machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
- machineconfig/scripts/python/devops_devapps_install.py +25 -20
- machineconfig/scripts/python/devops_update_repos.py +142 -57
- machineconfig/scripts/python/dotfile.py +16 -14
- machineconfig/scripts/python/fire_agents.py +24 -17
- machineconfig/scripts/python/fire_jobs.py +91 -55
- machineconfig/scripts/python/ftpx.py +24 -14
- machineconfig/scripts/python/get_zellij_cmd.py +8 -7
- machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
- machineconfig/scripts/python/helpers/helpers2.py +25 -14
- machineconfig/scripts/python/helpers/helpers4.py +44 -30
- machineconfig/scripts/python/helpers/helpers5.py +1 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
- machineconfig/scripts/python/mount_nfs.py +8 -15
- machineconfig/scripts/python/mount_nw_drive.py +10 -5
- machineconfig/scripts/python/mount_ssh.py +8 -6
- machineconfig/scripts/python/repos.py +215 -57
- machineconfig/scripts/python/snapshot.py +0 -1
- machineconfig/scripts/python/start_slidev.py +10 -5
- machineconfig/scripts/python/start_terminals.py +22 -16
- machineconfig/scripts/python/viewer_template.py +0 -1
- machineconfig/scripts/python/wifi_conn.py +49 -75
- machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
- machineconfig/settings/lf/linux/lfrc +1 -0
- machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
- machineconfig/utils/code.py +3 -3
- machineconfig/utils/installer.py +2 -2
- machineconfig/utils/installer_utils/installer_abc.py +3 -4
- machineconfig/utils/installer_utils/installer_class.py +6 -4
- machineconfig/utils/links.py +103 -33
- machineconfig/utils/notifications.py +52 -38
- machineconfig/utils/options.py +16 -23
- machineconfig/utils/path_reduced.py +239 -205
- machineconfig/utils/procs.py +1 -1
- machineconfig/utils/source_of_truth.py +27 -0
- machineconfig/utils/ssh.py +9 -29
- machineconfig/utils/terminal.py +4 -2
- machineconfig/utils/upgrade_packages.py +91 -0
- machineconfig/utils/utils2.py +1 -2
- machineconfig/utils/utils5.py +23 -11
- machineconfig/utils/ve.py +4 -1
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/METADATA +13 -13
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/RECORD +78 -86
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
- machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
- machineconfig/utils/utils.py +0 -97
- /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
- {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
654
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
716
|
-
|
|
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
|
-
|
|
721
|
-
|
|
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
|
-
|
|
726
|
-
|
|
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
|
-
|
|
731
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
machineconfig/utils/procs.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import psutil
|
|
4
4
|
from tqdm import tqdm
|
|
5
5
|
from pytz import timezone
|
|
6
|
-
from machineconfig.utils.
|
|
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
|