machineconfig 1.96__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.
- machineconfig/cluster/cloud_manager.py +22 -26
- machineconfig/cluster/data_transfer.py +2 -2
- machineconfig/cluster/distribute.py +0 -2
- machineconfig/cluster/file_manager.py +4 -4
- machineconfig/cluster/job_params.py +1 -1
- machineconfig/cluster/loader_runner.py +8 -8
- machineconfig/cluster/remote_machine.py +4 -4
- machineconfig/cluster/script_execution.py +2 -2
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +1 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +23 -23
- machineconfig/cluster/sessions_managers/wt_local.py +78 -76
- machineconfig/cluster/sessions_managers/wt_local_manager.py +91 -91
- machineconfig/cluster/sessions_managers/wt_remote.py +39 -39
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +94 -91
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +56 -54
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +18 -18
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +42 -42
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +36 -36
- machineconfig/cluster/sessions_managers/zellij_local.py +43 -46
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +139 -120
- machineconfig/cluster/sessions_managers/zellij_remote.py +35 -35
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +33 -33
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +25 -26
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +49 -49
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +5 -5
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +15 -15
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +11 -11
- machineconfig/cluster/templates/utils.py +3 -3
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
- machineconfig/jobs/python/check_installations.py +8 -9
- machineconfig/jobs/python/python_cargo_build_share.py +2 -2
- machineconfig/jobs/python/vscode/link_ve.py +7 -7
- machineconfig/jobs/python/vscode/select_interpreter.py +7 -7
- machineconfig/jobs/python/vscode/sync_code.py +5 -5
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +2 -2
- machineconfig/jobs/python_custom_installers/dev/aider.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/brave.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +5 -5
- machineconfig/jobs/python_custom_installers/dev/code.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/cursor.py +9 -9
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/espanso.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/goes.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/lvim.py +4 -4
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/redis.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -3
- machineconfig/jobs/python_custom_installers/dev/winget.py +27 -27
- machineconfig/jobs/python_custom_installers/docker.py +3 -3
- machineconfig/jobs/python_custom_installers/gh.py +7 -7
- machineconfig/jobs/python_custom_installers/hx.py +1 -1
- machineconfig/jobs/python_custom_installers/warp-cli.py +3 -3
- machineconfig/jobs/python_generic_installers/config.json +412 -389
- machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
- machineconfig/logger.py +50 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/profile/create.py +23 -16
- machineconfig/profile/create_hardlinks.py +8 -8
- machineconfig/profile/shell.py +41 -37
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/linux/devops +2 -2
- machineconfig/scripts/linux/fire +1 -0
- machineconfig/scripts/linux/fire_agents +0 -1
- machineconfig/scripts/linux/mcinit +27 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
- machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
- machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
- machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
- machineconfig/scripts/python/ai/mcinit.py +103 -0
- machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
- machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +47 -0
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +3 -3
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/cloud_copy.py +19 -18
- machineconfig/scripts/python/cloud_mount.py +9 -7
- machineconfig/scripts/python/cloud_repo_sync.py +11 -11
- machineconfig/scripts/python/cloud_sync.py +1 -1
- machineconfig/scripts/python/croshell.py +14 -14
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_add_identity.py +8 -6
- machineconfig/scripts/python/devops_add_ssh_key.py +18 -18
- machineconfig/scripts/python/devops_backup_retrieve.py +13 -13
- machineconfig/scripts/python/devops_devapps_install.py +3 -3
- machineconfig/scripts/python/devops_update_repos.py +1 -1
- machineconfig/scripts/python/dotfile.py +2 -2
- machineconfig/scripts/python/fire_agents.py +183 -41
- machineconfig/scripts/python/fire_jobs.py +17 -11
- machineconfig/scripts/python/ftpx.py +2 -2
- machineconfig/scripts/python/gh_models.py +94 -94
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
- machineconfig/scripts/python/helpers/helpers2.py +1 -1
- machineconfig/scripts/python/helpers/helpers4.py +8 -6
- machineconfig/scripts/python/helpers/helpers5.py +7 -7
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
- machineconfig/scripts/python/mount_nfs.py +3 -2
- machineconfig/scripts/python/mount_nw_drive.py +4 -4
- machineconfig/scripts/python/mount_ssh.py +3 -2
- machineconfig/scripts/python/repos.py +8 -8
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +8 -7
- machineconfig/scripts/python/start_terminals.py +1 -1
- machineconfig/scripts/python/viewer.py +40 -40
- machineconfig/scripts/python/wifi_conn.py +65 -66
- machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
- machineconfig/scripts/windows/mcinit.ps1 +4 -0
- machineconfig/settings/linters/.ruff.toml +2 -2
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
- machineconfig/settings/shells/wt/settings.json +8 -8
- machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +10 -7
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +9 -7
- machineconfig/utils/ai/browser_user_wrapper.py +5 -5
- machineconfig/utils/ai/generate_file_checklist.py +11 -12
- machineconfig/utils/ai/url2md.py +1 -1
- machineconfig/utils/cloud/onedrive/setup_oauth.py +4 -4
- machineconfig/utils/cloud/onedrive/transaction.py +129 -129
- machineconfig/utils/code.py +13 -6
- machineconfig/utils/installer.py +51 -53
- machineconfig/utils/installer_utils/installer_abc.py +21 -10
- machineconfig/utils/installer_utils/installer_class.py +42 -16
- machineconfig/utils/io_save.py +3 -15
- machineconfig/utils/options.py +10 -3
- machineconfig/utils/path.py +5 -0
- machineconfig/utils/path_reduced.py +201 -149
- machineconfig/utils/procs.py +23 -23
- machineconfig/utils/scheduling.py +11 -12
- machineconfig/utils/ssh.py +270 -0
- machineconfig/utils/terminal.py +180 -0
- machineconfig/utils/utils.py +1 -2
- machineconfig/utils/utils2.py +43 -0
- machineconfig/utils/utils5.py +163 -34
- machineconfig/utils/ve.py +2 -2
- {machineconfig-1.96.dist-info → machineconfig-2.0.dist-info}/METADATA +13 -8
- {machineconfig-1.96.dist-info → machineconfig-2.0.dist-info}/RECORD +163 -144
- machineconfig/cluster/self_ssh.py +0 -57
- {machineconfig-1.96.dist-info → machineconfig-2.0.dist-info}/WHEEL +0 -0
- {machineconfig-1.96.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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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) ->
|
|
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 =
|
|
408
|
+
raw = list(root.iterdir())
|
|
353
409
|
else:
|
|
354
|
-
raw =
|
|
355
|
-
res1 = raw.filter(lambda zip_path: fnmatch.fnmatch(zip_path.at, pattern)) # type: ignore
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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)
|