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