machineconfig 1.95__py3-none-any.whl → 1.97__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of machineconfig might be problematic. Click here for more details.

Files changed (504) hide show
  1. machineconfig/cluster/cloud_manager.py +445 -343
  2. machineconfig/cluster/data_transfer.py +63 -57
  3. machineconfig/cluster/distribute.py +284 -280
  4. machineconfig/cluster/file_manager.py +234 -237
  5. machineconfig/cluster/job_params.py +133 -133
  6. machineconfig/cluster/loader_runner.py +183 -149
  7. machineconfig/cluster/remote_machine.py +269 -252
  8. machineconfig/cluster/script_execution.py +215 -209
  9. machineconfig/cluster/script_notify_upon_completion.py +50 -43
  10. machineconfig/cluster/self_ssh.py +52 -54
  11. machineconfig/cluster/sessions_managers/__init__.py +0 -0
  12. machineconfig/cluster/sessions_managers/archive/__init__.py +0 -0
  13. machineconfig/{jobs/python → cluster/sessions_managers/archive}/create_zellij_template.py +5 -3
  14. machineconfig/cluster/sessions_managers/archive/session_managers.py +184 -0
  15. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  16. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +160 -0
  17. machineconfig/cluster/sessions_managers/wt_local.py +494 -0
  18. machineconfig/cluster/sessions_managers/wt_local_manager.py +577 -0
  19. machineconfig/cluster/sessions_managers/wt_remote.py +288 -0
  20. machineconfig/cluster/sessions_managers/wt_remote_manager.py +483 -0
  21. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +196 -0
  22. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +418 -0
  23. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +175 -0
  24. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +300 -0
  25. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +228 -0
  26. machineconfig/cluster/sessions_managers/zellij_local.py +418 -0
  27. machineconfig/cluster/sessions_managers/zellij_local_manager.py +533 -0
  28. machineconfig/cluster/sessions_managers/zellij_remote.py +229 -0
  29. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +188 -0
  30. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +64 -0
  31. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +126 -0
  32. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +334 -0
  33. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +68 -0
  34. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +119 -0
  35. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +85 -0
  36. machineconfig/cluster/templates/cli_click.py +0 -1
  37. machineconfig/cluster/templates/cli_gooey.py +102 -104
  38. machineconfig/cluster/templates/run_cloud.py +51 -51
  39. machineconfig/cluster/templates/run_cluster.py +103 -59
  40. machineconfig/cluster/templates/run_remote.py +57 -58
  41. machineconfig/cluster/templates/utils.py +69 -36
  42. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  43. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  44. machineconfig/jobs/python/check_installations.py +258 -190
  45. machineconfig/jobs/python/create_bootable_media.py +7 -3
  46. machineconfig/jobs/python/python_cargo_build_share.py +50 -50
  47. machineconfig/jobs/python/python_ve_symlink.py +6 -6
  48. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  49. machineconfig/jobs/python/vscode/api.py +1 -1
  50. machineconfig/jobs/python/vscode/link_ve.py +2 -2
  51. machineconfig/jobs/python/vscode/select_interpreter.py +9 -5
  52. machineconfig/jobs/python/vscode/sync_code.py +8 -5
  53. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  54. machineconfig/jobs/python_custom_installers/archive/ngrok.py +1 -1
  55. machineconfig/jobs/python_custom_installers/dev/alacritty.py +3 -2
  56. machineconfig/jobs/python_custom_installers/dev/brave.py +7 -3
  57. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +3 -4
  58. machineconfig/jobs/python_custom_installers/dev/code.py +3 -1
  59. machineconfig/jobs/python_custom_installers/dev/cursor.py +66 -5
  60. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +0 -1
  61. machineconfig/jobs/python_custom_installers/dev/espanso.py +13 -9
  62. machineconfig/jobs/python_custom_installers/dev/goes.py +2 -8
  63. machineconfig/jobs/python_custom_installers/dev/lvim.py +3 -2
  64. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +1 -1
  65. machineconfig/jobs/python_custom_installers/dev/redis.py +7 -3
  66. machineconfig/jobs/python_custom_installers/dev/wezterm.py +8 -4
  67. machineconfig/jobs/python_custom_installers/dev/winget.py +194 -0
  68. machineconfig/jobs/python_custom_installers/{dev/docker.py → docker.py} +8 -3
  69. machineconfig/jobs/python_custom_installers/gh.py +4 -3
  70. machineconfig/jobs/python_custom_installers/hx.py +9 -8
  71. machineconfig/jobs/python_custom_installers/scripts/linux/vscode.sh +97 -30
  72. machineconfig/jobs/python_custom_installers/{dev/warp-cli.py → warp-cli.py} +1 -1
  73. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  74. machineconfig/jobs/python_generic_installers/config.json +133 -9
  75. machineconfig/jobs/python_generic_installers/dev/config.json +208 -37
  76. machineconfig/jobs/python_generic_installers/update.py +3 -0
  77. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  78. machineconfig/jobs/python_linux_installers/config.json +42 -6
  79. machineconfig/jobs/python_linux_installers/dev/config.json +79 -11
  80. machineconfig/jobs/python_windows_installers/config.json +6 -0
  81. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  82. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  83. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  84. machineconfig/profile/create.py +5 -5
  85. machineconfig/profile/create_hardlinks.py +5 -5
  86. machineconfig/profile/shell.py +44 -17
  87. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  88. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  89. machineconfig/scripts/linux/fire_agents +27 -0
  90. machineconfig/scripts/linux/mcinit +27 -0
  91. machineconfig/scripts/linux/wifi_conn +24 -0
  92. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  93. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  94. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  95. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  96. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  97. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  98. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  99. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  100. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  106. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  108. machineconfig/scripts/python/ai/init.py +56 -0
  109. machineconfig/scripts/python/ai/rules/python/dev.md +31 -0
  110. machineconfig/scripts/python/archive/im2text.py +1 -3
  111. machineconfig/scripts/python/choose_wezterm_theme.py +3 -3
  112. machineconfig/scripts/python/cloud_copy.py +10 -10
  113. machineconfig/scripts/python/cloud_manager.py +77 -99
  114. machineconfig/scripts/python/cloud_mount.py +13 -12
  115. machineconfig/scripts/python/cloud_repo_sync.py +14 -11
  116. machineconfig/scripts/python/croshell.py +24 -21
  117. machineconfig/scripts/python/devops.py +12 -17
  118. machineconfig/scripts/python/devops_add_identity.py +32 -10
  119. machineconfig/scripts/python/devops_add_ssh_key.py +10 -10
  120. machineconfig/scripts/python/devops_backup_retrieve.py +9 -8
  121. machineconfig/scripts/python/devops_devapps_install.py +6 -6
  122. machineconfig/scripts/python/devops_update_repos.py +4 -3
  123. machineconfig/scripts/python/dotfile.py +10 -7
  124. machineconfig/scripts/python/fire_agents.py +69 -0
  125. machineconfig/scripts/python/fire_jobs.py +62 -65
  126. machineconfig/scripts/python/ftpx.py +8 -8
  127. machineconfig/scripts/python/get_zellij_cmd.py +3 -3
  128. machineconfig/scripts/python/gh_models.py +6 -4
  129. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  130. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  131. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  132. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  133. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  134. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  135. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  136. machineconfig/scripts/python/helpers/cloud_helpers.py +12 -12
  137. machineconfig/scripts/python/helpers/helpers2.py +9 -8
  138. machineconfig/scripts/python/helpers/helpers4.py +23 -35
  139. machineconfig/scripts/python/helpers/repo_sync_helpers.py +17 -16
  140. machineconfig/scripts/python/mount_nfs.py +8 -11
  141. machineconfig/scripts/python/mount_nw_drive.py +4 -4
  142. machineconfig/scripts/python/mount_ssh.py +2 -2
  143. machineconfig/scripts/python/onetimeshare.py +56 -57
  144. machineconfig/scripts/python/pomodoro.py +55 -55
  145. machineconfig/scripts/python/repos.py +26 -18
  146. machineconfig/scripts/python/scheduler.py +70 -53
  147. machineconfig/scripts/python/snapshot.py +21 -24
  148. machineconfig/scripts/python/start_slidev.py +6 -5
  149. machineconfig/scripts/python/start_terminals.py +3 -1
  150. machineconfig/scripts/python/viewer.py +5 -4
  151. machineconfig/scripts/python/viewer_template.py +138 -140
  152. machineconfig/scripts/python/wifi_conn.py +412 -60
  153. machineconfig/scripts/python/wsl_windows_transfer.py +18 -3
  154. machineconfig/scripts/windows/mcinit.ps1 +4 -0
  155. machineconfig/settings/linters/.pylintrc +6 -7
  156. machineconfig/settings/lvim/windows/config.lua +0 -0
  157. machineconfig/settings/shells/bash/init.sh +6 -0
  158. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +7 -6
  159. machineconfig/settings/shells/pwsh/init.ps1 +6 -6
  160. machineconfig/settings/shells/wt/settings.json +51 -266
  161. machineconfig/setup_linux/web_shortcuts/interactive.sh +5 -2
  162. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -6
  163. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +11 -9
  164. machineconfig/utils/ai/url2md.py +2 -2
  165. machineconfig/utils/cloud/onedrive/setup_oauth.py +59 -0
  166. machineconfig/utils/cloud/onedrive/transaction.py +796 -0
  167. machineconfig/utils/code.py +22 -13
  168. machineconfig/utils/installer.py +78 -35
  169. machineconfig/utils/installer_utils/installer_abc.py +7 -6
  170. machineconfig/utils/installer_utils/installer_class.py +44 -25
  171. machineconfig/utils/io_save.py +107 -0
  172. machineconfig/utils/links.py +19 -15
  173. machineconfig/utils/options.py +4 -8
  174. machineconfig/utils/path.py +91 -78
  175. machineconfig/utils/path_reduced.py +608 -0
  176. machineconfig/utils/procs.py +110 -45
  177. machineconfig/utils/scheduling.py +312 -222
  178. machineconfig/utils/utils.py +7 -7
  179. machineconfig/utils/utils2.py +42 -0
  180. machineconfig/utils/utils5.py +84 -0
  181. machineconfig/utils/ve.py +49 -87
  182. {machineconfig-1.95.dist-info → machineconfig-1.97.dist-info}/METADATA +2 -2
  183. machineconfig-1.97.dist-info/RECORD +442 -0
  184. machineconfig/cluster/session_managers.py +0 -183
  185. machineconfig/cluster/templates/f.py +0 -4
  186. machineconfig/jobs/python/__pycache__/check_installations.cpython-311.pyc +0 -0
  187. machineconfig/jobs/python/__pycache__/checkout_version.cpython-311.pyc +0 -0
  188. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  189. machineconfig/jobs/python/checkout_version.py +0 -123
  190. machineconfig/jobs/python/vscode/__pycache__/api.cpython-311.pyc +0 -0
  191. machineconfig/jobs/python/vscode/__pycache__/link_ve.cpython-311.pyc +0 -0
  192. machineconfig/jobs/python_custom_installers/__pycache__/hx.cpython-311.pyc +0 -0
  193. machineconfig/jobs/python_windows_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  194. machineconfig/scripts/python/.mypy_cache/.gitignore +0 -2
  195. machineconfig/scripts/python/.mypy_cache/3.11/@plugins_snapshot.json +0 -1
  196. machineconfig/scripts/python/.mypy_cache/3.11/__future__.data.json +0 -1
  197. machineconfig/scripts/python/.mypy_cache/3.11/__future__.meta.json +0 -1
  198. machineconfig/scripts/python/.mypy_cache/3.11/_ast.data.json +0 -1
  199. machineconfig/scripts/python/.mypy_cache/3.11/_ast.meta.json +0 -1
  200. machineconfig/scripts/python/.mypy_cache/3.11/_bz2.data.json +0 -1
  201. machineconfig/scripts/python/.mypy_cache/3.11/_bz2.meta.json +0 -1
  202. machineconfig/scripts/python/.mypy_cache/3.11/_codecs.data.json +0 -1
  203. machineconfig/scripts/python/.mypy_cache/3.11/_codecs.meta.json +0 -1
  204. machineconfig/scripts/python/.mypy_cache/3.11/_collections_abc.data.json +0 -1
  205. machineconfig/scripts/python/.mypy_cache/3.11/_collections_abc.meta.json +0 -1
  206. machineconfig/scripts/python/.mypy_cache/3.11/_compression.data.json +0 -1
  207. machineconfig/scripts/python/.mypy_cache/3.11/_compression.meta.json +0 -1
  208. machineconfig/scripts/python/.mypy_cache/3.11/_decimal.data.json +0 -1
  209. machineconfig/scripts/python/.mypy_cache/3.11/_decimal.meta.json +0 -1
  210. machineconfig/scripts/python/.mypy_cache/3.11/_frozen_importlib.data.json +0 -1
  211. machineconfig/scripts/python/.mypy_cache/3.11/_frozen_importlib.meta.json +0 -1
  212. machineconfig/scripts/python/.mypy_cache/3.11/_frozen_importlib_external.data.json +0 -1
  213. machineconfig/scripts/python/.mypy_cache/3.11/_frozen_importlib_external.meta.json +0 -1
  214. machineconfig/scripts/python/.mypy_cache/3.11/_io.data.json +0 -1
  215. machineconfig/scripts/python/.mypy_cache/3.11/_io.meta.json +0 -1
  216. machineconfig/scripts/python/.mypy_cache/3.11/_locale.data.json +0 -1
  217. machineconfig/scripts/python/.mypy_cache/3.11/_locale.meta.json +0 -1
  218. machineconfig/scripts/python/.mypy_cache/3.11/_stat.data.json +0 -1
  219. machineconfig/scripts/python/.mypy_cache/3.11/_stat.meta.json +0 -1
  220. machineconfig/scripts/python/.mypy_cache/3.11/_struct.data.json +0 -1
  221. machineconfig/scripts/python/.mypy_cache/3.11/_struct.meta.json +0 -1
  222. machineconfig/scripts/python/.mypy_cache/3.11/_thread.data.json +0 -1
  223. machineconfig/scripts/python/.mypy_cache/3.11/_thread.meta.json +0 -1
  224. machineconfig/scripts/python/.mypy_cache/3.11/_typeshed/__init__.data.json +0 -1
  225. machineconfig/scripts/python/.mypy_cache/3.11/_typeshed/__init__.meta.json +0 -1
  226. machineconfig/scripts/python/.mypy_cache/3.11/_typeshed/importlib.data.json +0 -1
  227. machineconfig/scripts/python/.mypy_cache/3.11/_typeshed/importlib.meta.json +0 -1
  228. machineconfig/scripts/python/.mypy_cache/3.11/_warnings.data.json +0 -1
  229. machineconfig/scripts/python/.mypy_cache/3.11/_warnings.meta.json +0 -1
  230. machineconfig/scripts/python/.mypy_cache/3.11/_weakref.data.json +0 -1
  231. machineconfig/scripts/python/.mypy_cache/3.11/_weakref.meta.json +0 -1
  232. machineconfig/scripts/python/.mypy_cache/3.11/_weakrefset.data.json +0 -1
  233. machineconfig/scripts/python/.mypy_cache/3.11/_weakrefset.meta.json +0 -1
  234. machineconfig/scripts/python/.mypy_cache/3.11/abc.data.json +0 -1
  235. machineconfig/scripts/python/.mypy_cache/3.11/abc.meta.json +0 -1
  236. machineconfig/scripts/python/.mypy_cache/3.11/argparse.data.json +0 -1
  237. machineconfig/scripts/python/.mypy_cache/3.11/argparse.meta.json +0 -1
  238. machineconfig/scripts/python/.mypy_cache/3.11/ast.data.json +0 -1
  239. machineconfig/scripts/python/.mypy_cache/3.11/ast.meta.json +0 -1
  240. machineconfig/scripts/python/.mypy_cache/3.11/binascii.data.json +0 -1
  241. machineconfig/scripts/python/.mypy_cache/3.11/binascii.meta.json +0 -1
  242. machineconfig/scripts/python/.mypy_cache/3.11/builtins.data.json +0 -1
  243. machineconfig/scripts/python/.mypy_cache/3.11/builtins.meta.json +0 -1
  244. machineconfig/scripts/python/.mypy_cache/3.11/bz2.data.json +0 -1
  245. machineconfig/scripts/python/.mypy_cache/3.11/bz2.meta.json +0 -1
  246. machineconfig/scripts/python/.mypy_cache/3.11/calendar.data.json +0 -1
  247. machineconfig/scripts/python/.mypy_cache/3.11/calendar.meta.json +0 -1
  248. machineconfig/scripts/python/.mypy_cache/3.11/codecs.data.json +0 -1
  249. machineconfig/scripts/python/.mypy_cache/3.11/codecs.meta.json +0 -1
  250. machineconfig/scripts/python/.mypy_cache/3.11/collections/__init__.data.json +0 -1
  251. machineconfig/scripts/python/.mypy_cache/3.11/collections/__init__.meta.json +0 -1
  252. machineconfig/scripts/python/.mypy_cache/3.11/collections/abc.data.json +0 -1
  253. machineconfig/scripts/python/.mypy_cache/3.11/collections/abc.meta.json +0 -1
  254. machineconfig/scripts/python/.mypy_cache/3.11/configparser.data.json +0 -1
  255. machineconfig/scripts/python/.mypy_cache/3.11/configparser.meta.json +0 -1
  256. machineconfig/scripts/python/.mypy_cache/3.11/contextlib.data.json +0 -1
  257. machineconfig/scripts/python/.mypy_cache/3.11/contextlib.meta.json +0 -1
  258. machineconfig/scripts/python/.mypy_cache/3.11/dataclasses.data.json +0 -1
  259. machineconfig/scripts/python/.mypy_cache/3.11/dataclasses.meta.json +0 -1
  260. machineconfig/scripts/python/.mypy_cache/3.11/datetime.data.json +0 -1
  261. machineconfig/scripts/python/.mypy_cache/3.11/datetime.meta.json +0 -1
  262. machineconfig/scripts/python/.mypy_cache/3.11/decimal.data.json +0 -1
  263. machineconfig/scripts/python/.mypy_cache/3.11/decimal.meta.json +0 -1
  264. machineconfig/scripts/python/.mypy_cache/3.11/dis.data.json +0 -1
  265. machineconfig/scripts/python/.mypy_cache/3.11/dis.meta.json +0 -1
  266. machineconfig/scripts/python/.mypy_cache/3.11/email/__init__.data.json +0 -1
  267. machineconfig/scripts/python/.mypy_cache/3.11/email/__init__.meta.json +0 -1
  268. machineconfig/scripts/python/.mypy_cache/3.11/email/_policybase.data.json +0 -1
  269. machineconfig/scripts/python/.mypy_cache/3.11/email/_policybase.meta.json +0 -1
  270. machineconfig/scripts/python/.mypy_cache/3.11/email/charset.data.json +0 -1
  271. machineconfig/scripts/python/.mypy_cache/3.11/email/charset.meta.json +0 -1
  272. machineconfig/scripts/python/.mypy_cache/3.11/email/contentmanager.data.json +0 -1
  273. machineconfig/scripts/python/.mypy_cache/3.11/email/contentmanager.meta.json +0 -1
  274. machineconfig/scripts/python/.mypy_cache/3.11/email/errors.data.json +0 -1
  275. machineconfig/scripts/python/.mypy_cache/3.11/email/errors.meta.json +0 -1
  276. machineconfig/scripts/python/.mypy_cache/3.11/email/header.data.json +0 -1
  277. machineconfig/scripts/python/.mypy_cache/3.11/email/header.meta.json +0 -1
  278. machineconfig/scripts/python/.mypy_cache/3.11/email/message.data.json +0 -1
  279. machineconfig/scripts/python/.mypy_cache/3.11/email/message.meta.json +0 -1
  280. machineconfig/scripts/python/.mypy_cache/3.11/email/policy.data.json +0 -1
  281. machineconfig/scripts/python/.mypy_cache/3.11/email/policy.meta.json +0 -1
  282. machineconfig/scripts/python/.mypy_cache/3.11/enum.data.json +0 -1
  283. machineconfig/scripts/python/.mypy_cache/3.11/enum.meta.json +0 -1
  284. machineconfig/scripts/python/.mypy_cache/3.11/fnmatch.data.json +0 -1
  285. machineconfig/scripts/python/.mypy_cache/3.11/fnmatch.meta.json +0 -1
  286. machineconfig/scripts/python/.mypy_cache/3.11/functools.data.json +0 -1
  287. machineconfig/scripts/python/.mypy_cache/3.11/functools.meta.json +0 -1
  288. machineconfig/scripts/python/.mypy_cache/3.11/gc.data.json +0 -1
  289. machineconfig/scripts/python/.mypy_cache/3.11/gc.meta.json +0 -1
  290. machineconfig/scripts/python/.mypy_cache/3.11/genericpath.data.json +0 -1
  291. machineconfig/scripts/python/.mypy_cache/3.11/genericpath.meta.json +0 -1
  292. machineconfig/scripts/python/.mypy_cache/3.11/getpass.data.json +0 -1
  293. machineconfig/scripts/python/.mypy_cache/3.11/getpass.meta.json +0 -1
  294. machineconfig/scripts/python/.mypy_cache/3.11/git/__init__.data.json +0 -1
  295. machineconfig/scripts/python/.mypy_cache/3.11/git/__init__.meta.json +0 -1
  296. machineconfig/scripts/python/.mypy_cache/3.11/git/cmd.data.json +0 -1
  297. machineconfig/scripts/python/.mypy_cache/3.11/git/cmd.meta.json +0 -1
  298. machineconfig/scripts/python/.mypy_cache/3.11/git/compat.data.json +0 -1
  299. machineconfig/scripts/python/.mypy_cache/3.11/git/compat.meta.json +0 -1
  300. machineconfig/scripts/python/.mypy_cache/3.11/git/config.data.json +0 -1
  301. machineconfig/scripts/python/.mypy_cache/3.11/git/config.meta.json +0 -1
  302. machineconfig/scripts/python/.mypy_cache/3.11/git/db.data.json +0 -1
  303. machineconfig/scripts/python/.mypy_cache/3.11/git/db.meta.json +0 -1
  304. machineconfig/scripts/python/.mypy_cache/3.11/git/diff.data.json +0 -1
  305. machineconfig/scripts/python/.mypy_cache/3.11/git/diff.meta.json +0 -1
  306. machineconfig/scripts/python/.mypy_cache/3.11/git/exc.data.json +0 -1
  307. machineconfig/scripts/python/.mypy_cache/3.11/git/exc.meta.json +0 -1
  308. machineconfig/scripts/python/.mypy_cache/3.11/git/index/__init__.data.json +0 -1
  309. machineconfig/scripts/python/.mypy_cache/3.11/git/index/__init__.meta.json +0 -1
  310. machineconfig/scripts/python/.mypy_cache/3.11/git/index/base.data.json +0 -1
  311. machineconfig/scripts/python/.mypy_cache/3.11/git/index/base.meta.json +0 -1
  312. machineconfig/scripts/python/.mypy_cache/3.11/git/index/fun.data.json +0 -1
  313. machineconfig/scripts/python/.mypy_cache/3.11/git/index/fun.meta.json +0 -1
  314. machineconfig/scripts/python/.mypy_cache/3.11/git/index/typ.data.json +0 -1
  315. machineconfig/scripts/python/.mypy_cache/3.11/git/index/typ.meta.json +0 -1
  316. machineconfig/scripts/python/.mypy_cache/3.11/git/index/util.data.json +0 -1
  317. machineconfig/scripts/python/.mypy_cache/3.11/git/index/util.meta.json +0 -1
  318. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/__init__.data.json +0 -1
  319. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/__init__.meta.json +0 -1
  320. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/base.data.json +0 -1
  321. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/base.meta.json +0 -1
  322. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/blob.data.json +0 -1
  323. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/blob.meta.json +0 -1
  324. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/commit.data.json +0 -1
  325. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/commit.meta.json +0 -1
  326. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/fun.data.json +0 -1
  327. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/fun.meta.json +0 -1
  328. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/__init__.data.json +0 -1
  329. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/__init__.meta.json +0 -1
  330. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/base.data.json +0 -1
  331. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/base.meta.json +0 -1
  332. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/root.data.json +0 -1
  333. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/root.meta.json +0 -1
  334. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/util.data.json +0 -1
  335. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/submodule/util.meta.json +0 -1
  336. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/tag.data.json +0 -1
  337. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/tag.meta.json +0 -1
  338. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/tree.data.json +0 -1
  339. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/tree.meta.json +0 -1
  340. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/util.data.json +0 -1
  341. machineconfig/scripts/python/.mypy_cache/3.11/git/objects/util.meta.json +0 -1
  342. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/__init__.data.json +0 -1
  343. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/__init__.meta.json +0 -1
  344. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/head.data.json +0 -1
  345. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/head.meta.json +0 -1
  346. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/log.data.json +0 -1
  347. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/log.meta.json +0 -1
  348. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/reference.data.json +0 -1
  349. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/reference.meta.json +0 -1
  350. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/remote.data.json +0 -1
  351. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/remote.meta.json +0 -1
  352. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/symbolic.data.json +0 -1
  353. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/symbolic.meta.json +0 -1
  354. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/tag.data.json +0 -1
  355. machineconfig/scripts/python/.mypy_cache/3.11/git/refs/tag.meta.json +0 -1
  356. machineconfig/scripts/python/.mypy_cache/3.11/git/remote.data.json +0 -1
  357. machineconfig/scripts/python/.mypy_cache/3.11/git/remote.meta.json +0 -1
  358. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/__init__.data.json +0 -1
  359. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/__init__.meta.json +0 -1
  360. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/base.data.json +0 -1
  361. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/base.meta.json +0 -1
  362. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/fun.data.json +0 -1
  363. machineconfig/scripts/python/.mypy_cache/3.11/git/repo/fun.meta.json +0 -1
  364. machineconfig/scripts/python/.mypy_cache/3.11/git/types.data.json +0 -1
  365. machineconfig/scripts/python/.mypy_cache/3.11/git/types.meta.json +0 -1
  366. machineconfig/scripts/python/.mypy_cache/3.11/git/util.data.json +0 -1
  367. machineconfig/scripts/python/.mypy_cache/3.11/git/util.meta.json +0 -1
  368. machineconfig/scripts/python/.mypy_cache/3.11/glob.data.json +0 -1
  369. machineconfig/scripts/python/.mypy_cache/3.11/glob.meta.json +0 -1
  370. machineconfig/scripts/python/.mypy_cache/3.11/gzip.data.json +0 -1
  371. machineconfig/scripts/python/.mypy_cache/3.11/gzip.meta.json +0 -1
  372. machineconfig/scripts/python/.mypy_cache/3.11/importlib/__init__.data.json +0 -1
  373. machineconfig/scripts/python/.mypy_cache/3.11/importlib/__init__.meta.json +0 -1
  374. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_abc.data.json +0 -1
  375. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_abc.meta.json +0 -1
  376. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_bootstrap.data.json +0 -1
  377. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_bootstrap.meta.json +0 -1
  378. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_bootstrap_external.data.json +0 -1
  379. machineconfig/scripts/python/.mypy_cache/3.11/importlib/_bootstrap_external.meta.json +0 -1
  380. machineconfig/scripts/python/.mypy_cache/3.11/importlib/abc.data.json +0 -1
  381. machineconfig/scripts/python/.mypy_cache/3.11/importlib/abc.meta.json +0 -1
  382. machineconfig/scripts/python/.mypy_cache/3.11/importlib/machinery.data.json +0 -1
  383. machineconfig/scripts/python/.mypy_cache/3.11/importlib/machinery.meta.json +0 -1
  384. machineconfig/scripts/python/.mypy_cache/3.11/importlib/metadata/__init__.data.json +0 -1
  385. machineconfig/scripts/python/.mypy_cache/3.11/importlib/metadata/__init__.meta.json +0 -1
  386. machineconfig/scripts/python/.mypy_cache/3.11/importlib/metadata/_meta.data.json +0 -1
  387. machineconfig/scripts/python/.mypy_cache/3.11/importlib/metadata/_meta.meta.json +0 -1
  388. machineconfig/scripts/python/.mypy_cache/3.11/importlib/readers.data.json +0 -1
  389. machineconfig/scripts/python/.mypy_cache/3.11/importlib/readers.meta.json +0 -1
  390. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/__init__.data.json +0 -1
  391. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/__init__.meta.json +0 -1
  392. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/_common.data.json +0 -1
  393. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/_common.meta.json +0 -1
  394. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/abc.data.json +0 -1
  395. machineconfig/scripts/python/.mypy_cache/3.11/importlib/resources/abc.meta.json +0 -1
  396. machineconfig/scripts/python/.mypy_cache/3.11/inspect.data.json +0 -1
  397. machineconfig/scripts/python/.mypy_cache/3.11/inspect.meta.json +0 -1
  398. machineconfig/scripts/python/.mypy_cache/3.11/io.data.json +0 -1
  399. machineconfig/scripts/python/.mypy_cache/3.11/io.meta.json +0 -1
  400. machineconfig/scripts/python/.mypy_cache/3.11/itertools.data.json +0 -1
  401. machineconfig/scripts/python/.mypy_cache/3.11/itertools.meta.json +0 -1
  402. machineconfig/scripts/python/.mypy_cache/3.11/locale.data.json +0 -1
  403. machineconfig/scripts/python/.mypy_cache/3.11/locale.meta.json +0 -1
  404. machineconfig/scripts/python/.mypy_cache/3.11/logging/__init__.data.json +0 -1
  405. machineconfig/scripts/python/.mypy_cache/3.11/logging/__init__.meta.json +0 -1
  406. machineconfig/scripts/python/.mypy_cache/3.11/mimetypes.data.json +0 -1
  407. machineconfig/scripts/python/.mypy_cache/3.11/mimetypes.meta.json +0 -1
  408. machineconfig/scripts/python/.mypy_cache/3.11/mmap.data.json +0 -1
  409. machineconfig/scripts/python/.mypy_cache/3.11/mmap.meta.json +0 -1
  410. machineconfig/scripts/python/.mypy_cache/3.11/numbers.data.json +0 -1
  411. machineconfig/scripts/python/.mypy_cache/3.11/numbers.meta.json +0 -1
  412. machineconfig/scripts/python/.mypy_cache/3.11/opcode.data.json +0 -1
  413. machineconfig/scripts/python/.mypy_cache/3.11/opcode.meta.json +0 -1
  414. machineconfig/scripts/python/.mypy_cache/3.11/os/__init__.data.json +0 -1
  415. machineconfig/scripts/python/.mypy_cache/3.11/os/__init__.meta.json +0 -1
  416. machineconfig/scripts/python/.mypy_cache/3.11/os/path.data.json +0 -1
  417. machineconfig/scripts/python/.mypy_cache/3.11/os/path.meta.json +0 -1
  418. machineconfig/scripts/python/.mypy_cache/3.11/pathlib.data.json +0 -1
  419. machineconfig/scripts/python/.mypy_cache/3.11/pathlib.meta.json +0 -1
  420. machineconfig/scripts/python/.mypy_cache/3.11/platform.data.json +0 -1
  421. machineconfig/scripts/python/.mypy_cache/3.11/platform.meta.json +0 -1
  422. machineconfig/scripts/python/.mypy_cache/3.11/posixpath.data.json +0 -1
  423. machineconfig/scripts/python/.mypy_cache/3.11/posixpath.meta.json +0 -1
  424. machineconfig/scripts/python/.mypy_cache/3.11/re.data.json +0 -1
  425. machineconfig/scripts/python/.mypy_cache/3.11/re.meta.json +0 -1
  426. machineconfig/scripts/python/.mypy_cache/3.11/resource.data.json +0 -1
  427. machineconfig/scripts/python/.mypy_cache/3.11/resource.meta.json +0 -1
  428. machineconfig/scripts/python/.mypy_cache/3.11/shlex.data.json +0 -1
  429. machineconfig/scripts/python/.mypy_cache/3.11/shlex.meta.json +0 -1
  430. machineconfig/scripts/python/.mypy_cache/3.11/shutil.data.json +0 -1
  431. machineconfig/scripts/python/.mypy_cache/3.11/shutil.meta.json +0 -1
  432. machineconfig/scripts/python/.mypy_cache/3.11/signal.data.json +0 -1
  433. machineconfig/scripts/python/.mypy_cache/3.11/signal.meta.json +0 -1
  434. machineconfig/scripts/python/.mypy_cache/3.11/src/__init__.data.json +0 -1
  435. machineconfig/scripts/python/.mypy_cache/3.11/src/__init__.meta.json +0 -1
  436. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/__init__.data.json +0 -1
  437. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/__init__.meta.json +0 -1
  438. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/scripts/__init__.data.json +0 -1
  439. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/scripts/__init__.meta.json +0 -1
  440. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/scripts/python/__init__.data.json +0 -1
  441. machineconfig/scripts/python/.mypy_cache/3.11/src/machineconfig/scripts/python/__init__.meta.json +0 -1
  442. machineconfig/scripts/python/.mypy_cache/3.11/sre_compile.data.json +0 -1
  443. machineconfig/scripts/python/.mypy_cache/3.11/sre_compile.meta.json +0 -1
  444. machineconfig/scripts/python/.mypy_cache/3.11/sre_constants.data.json +0 -1
  445. machineconfig/scripts/python/.mypy_cache/3.11/sre_constants.meta.json +0 -1
  446. machineconfig/scripts/python/.mypy_cache/3.11/sre_parse.data.json +0 -1
  447. machineconfig/scripts/python/.mypy_cache/3.11/sre_parse.meta.json +0 -1
  448. machineconfig/scripts/python/.mypy_cache/3.11/stat.data.json +0 -1
  449. machineconfig/scripts/python/.mypy_cache/3.11/stat.meta.json +0 -1
  450. machineconfig/scripts/python/.mypy_cache/3.11/string.data.json +0 -1
  451. machineconfig/scripts/python/.mypy_cache/3.11/string.meta.json +0 -1
  452. machineconfig/scripts/python/.mypy_cache/3.11/struct.data.json +0 -1
  453. machineconfig/scripts/python/.mypy_cache/3.11/struct.meta.json +0 -1
  454. machineconfig/scripts/python/.mypy_cache/3.11/subprocess.data.json +0 -1
  455. machineconfig/scripts/python/.mypy_cache/3.11/subprocess.meta.json +0 -1
  456. machineconfig/scripts/python/.mypy_cache/3.11/sys/__init__.data.json +0 -1
  457. machineconfig/scripts/python/.mypy_cache/3.11/sys/__init__.meta.json +0 -1
  458. machineconfig/scripts/python/.mypy_cache/3.11/tarfile.data.json +0 -1
  459. machineconfig/scripts/python/.mypy_cache/3.11/tarfile.meta.json +0 -1
  460. machineconfig/scripts/python/.mypy_cache/3.11/tempfile.data.json +0 -1
  461. machineconfig/scripts/python/.mypy_cache/3.11/tempfile.meta.json +0 -1
  462. machineconfig/scripts/python/.mypy_cache/3.11/textwrap.data.json +0 -1
  463. machineconfig/scripts/python/.mypy_cache/3.11/textwrap.meta.json +0 -1
  464. machineconfig/scripts/python/.mypy_cache/3.11/threading.data.json +0 -1
  465. machineconfig/scripts/python/.mypy_cache/3.11/threading.meta.json +0 -1
  466. machineconfig/scripts/python/.mypy_cache/3.11/time.data.json +0 -1
  467. machineconfig/scripts/python/.mypy_cache/3.11/time.meta.json +0 -1
  468. machineconfig/scripts/python/.mypy_cache/3.11/types.data.json +0 -1
  469. machineconfig/scripts/python/.mypy_cache/3.11/types.meta.json +0 -1
  470. machineconfig/scripts/python/.mypy_cache/3.11/typing.data.json +0 -1
  471. machineconfig/scripts/python/.mypy_cache/3.11/typing.meta.json +0 -1
  472. machineconfig/scripts/python/.mypy_cache/3.11/typing_extensions.data.json +0 -1
  473. machineconfig/scripts/python/.mypy_cache/3.11/typing_extensions.meta.json +0 -1
  474. machineconfig/scripts/python/.mypy_cache/3.11/urllib/__init__.data.json +0 -1
  475. machineconfig/scripts/python/.mypy_cache/3.11/urllib/__init__.meta.json +0 -1
  476. machineconfig/scripts/python/.mypy_cache/3.11/urllib/parse.data.json +0 -1
  477. machineconfig/scripts/python/.mypy_cache/3.11/urllib/parse.meta.json +0 -1
  478. machineconfig/scripts/python/.mypy_cache/3.11/uuid.data.json +0 -1
  479. machineconfig/scripts/python/.mypy_cache/3.11/uuid.meta.json +0 -1
  480. machineconfig/scripts/python/.mypy_cache/3.11/warnings.data.json +0 -1
  481. machineconfig/scripts/python/.mypy_cache/3.11/warnings.meta.json +0 -1
  482. machineconfig/scripts/python/.mypy_cache/3.11/weakref.data.json +0 -1
  483. machineconfig/scripts/python/.mypy_cache/3.11/weakref.meta.json +0 -1
  484. machineconfig/scripts/python/.mypy_cache/3.11/zipfile/__init__.data.json +0 -1
  485. machineconfig/scripts/python/.mypy_cache/3.11/zipfile/__init__.meta.json +0 -1
  486. machineconfig/scripts/python/.mypy_cache/3.11/zlib.data.json +0 -1
  487. machineconfig/scripts/python/.mypy_cache/3.11/zlib.meta.json +0 -1
  488. machineconfig/scripts/python/.mypy_cache/CACHEDIR.TAG +0 -3
  489. machineconfig/scripts/python/__pycache__/cloud_repo_sync.cpython-311.pyc +0 -0
  490. machineconfig/scripts/python/__pycache__/gh_models.cpython-311.pyc +0 -0
  491. machineconfig/scripts/python/__pycache__/url2md.cpython-311.pyc +0 -0
  492. machineconfig/scripts/python/__pycache__/viewer.cpython-311.pyc +0 -0
  493. machineconfig/scripts/python/__pycache__/vscode_api.cpython-311.pyc +0 -0
  494. machineconfig/settings/__pycache__/__init__.cpython-311.pyc +0 -0
  495. machineconfig/settings/linters/.ruff_cache/.gitignore +0 -2
  496. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +0 -1
  497. machineconfig/settings/shells/ipy/profiles/default/__pycache__/__init__.cpython-311.pyc +0 -0
  498. machineconfig/settings/shells/ipy/profiles/default/startup/__pycache__/__init__.cpython-311.pyc +0 -0
  499. machineconfig/settings/shells/ipy/profiles/default/startup/__pycache__/playext.cpython-311.pyc +0 -0
  500. machineconfig/utils/ve_utils/ve1.py +0 -111
  501. machineconfig/utils/ve_utils/ve2.py +0 -155
  502. machineconfig-1.95.dist-info/RECORD +0 -712
  503. {machineconfig-1.95.dist-info → machineconfig-1.97.dist-info}/WHEEL +0 -0
  504. {machineconfig-1.95.dist-info → machineconfig-1.97.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,608 @@
1
+
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
+
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ import sys
13
+ import subprocess
14
+ from typing import Any, Optional, Union, Callable, TypeAlias, Literal
15
+
16
+
17
+ OPLike: TypeAlias = Union[str, 'P', Path, None]
18
+ PLike: TypeAlias = Union[str, 'P', Path]
19
+ FILE_MODE: TypeAlias = Literal['r', 'w', 'x', 'a']
20
+ SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
21
+
22
+
23
+ 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
+ lines, bingo = txt_raw.split("\n"), False
25
+ if not replace_line: # no need for line splitting
26
+ assert isinstance(txt_alt, str), f"txt_alt must be a string if notfound_append is True. It is not: {txt_alt}"
27
+ if txt_search in txt_raw: return txt_raw.replace(txt_search, txt_alt)
28
+ return txt_raw + "\n" + txt_alt if notfound_append else txt_raw
29
+ for idx, line in enumerate(lines):
30
+ if txt_search in line:
31
+ if isinstance(txt_alt, str): lines[idx] = txt_alt
32
+ elif callable(txt_alt): lines[idx] = txt_alt(line)
33
+ bingo = True
34
+ if strict and not bingo: raise ValueError(f"txt_search `{txt_search}` not found in txt_raw `{txt_raw}`")
35
+ if bingo is False and notfound_append is True:
36
+ assert isinstance(txt_alt, str), f"txt_alt must be a string if notfound_append is True. It is not: {txt_alt}"
37
+ if prepend: lines.insert(0, txt_alt)
38
+ else: lines.append(txt_alt) # txt not found, add it anyway.
39
+ return "\n".join(lines)
40
+
41
+ class P(type(Path()), Path): # type: ignore # pylint: disable=E0241
42
+ # ============= Path management ==================
43
+ """ 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.
44
+ However, there is a flag `orig` that makes the function return orignal path object `self` as opposed to the new one pointing to new object.
45
+ Additionally, the fate of the original object can be decided by a flag `inplace` which means `replace` it defaults to False and in essence, it deletes the original underlying object.
46
+ This can be seen in `zip` and `encrypt` but not in `copy`, `move`, `retitle` because the fate of original file is dictated already.
47
+ Furthermore, those methods are accompanied with print statement explaining what happened to the object."""
48
+ def delete(self, sure: bool = False, verbose: bool = True) -> 'P': # slf = self.expanduser().resolve() don't resolve symlinks.
49
+ if not sure:
50
+ if verbose: print(f"❌ Did NOT DELETE because user is not sure. file: {repr(self)}.")
51
+ return self
52
+ if not self.exists():
53
+ self.unlink(missing_ok=True)
54
+ if verbose: print(f"❌ Could NOT DELETE nonexisting file {repr(self)}. ")
55
+ return self # broken symlinks exhibit funny existence behaviour, catch them here.
56
+ if self.is_file() or self.is_symlink(): self.unlink(missing_ok=True)
57
+ else:
58
+ import shutil
59
+ shutil.rmtree(self, ignore_errors=False)
60
+ if verbose: print(f"🗑️ ❌ DELETED {repr(self)}.")
61
+ return self
62
+ def move(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, rel2it: bool = False, overwrite: bool = False, verbose: bool = True, parents: bool = True, content: bool = False) -> 'P':
63
+ path = self._resolve_path(folder=folder, name=name, path=path, default_name=self.absolute().name, rel2it=rel2it)
64
+ if parents: path.parent.mkdir(parents=True, exist_ok=True)
65
+ slf = self.expanduser().resolve()
66
+ if content:
67
+ assert self.is_dir(), NotADirectoryError(f"💥 When `content` flag is set to True, path must be a directory. It is not: `{repr(self)}`")
68
+ self.search("*").apply(lambda x: x.move(folder=path.parent, content=False, overwrite=overwrite))
69
+ return path # contents live within this directory.
70
+ if overwrite:
71
+ tmp_path = slf.rename(path.parent.absolute() / randstr())
72
+ path.delete(sure=True, verbose=verbose)
73
+ tmp_path.rename(path) # works if moving a path up and parent has same name
74
+ else:
75
+ try:
76
+ slf.rename(path) # self._return(res=path, inplace=True, operation='rename', orig=False, verbose=verbose, strict=True, msg='')
77
+ except OSError as oe: # OSError: [Errno 18] Invalid cross-device link:
78
+ # https://stackoverflow.com/questions/42392600/oserror-errno-18-invalid-cross-device-link
79
+ import shutil
80
+ shutil.move(str(slf), str(path))
81
+ _ = oe
82
+ if verbose: print(f"🚚 MOVED {repr(self)} ==> {repr(path)}`")
83
+ return path
84
+ def copy(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, content: bool = False, verbose: bool = True, append: Optional[str] = None, overwrite: bool = False, orig: bool = False) -> 'P': # tested %100 # TODO: replace `content` flag with ability to interpret "*" in resolve method.
85
+ dest = self._resolve_path(folder=folder, name=name, path=path, default_name=self.name, rel2it=False)
86
+ dest = dest.expanduser().resolve()
87
+ dest.parent.mkdir(parents=True, exist_ok=True)
88
+ slf = self.expanduser().resolve()
89
+ if dest == slf:
90
+ dest = self.append(append if append is not None else f"_copy_{randstr()}")
91
+ if not content and overwrite and dest.exists(): dest.delete(sure=True)
92
+ if not content and not overwrite and dest.exists(): raise FileExistsError(f"💥 Destination already exists: {repr(dest)}")
93
+ if slf.is_file():
94
+ import shutil
95
+ shutil.copy(str(slf), str(dest))
96
+ if verbose: print(f"🖨️ COPIED {repr(slf)} ==> {repr(dest)}")
97
+ elif slf.is_dir():
98
+ dest = dest.parent if content else dest
99
+ # from distutils.dir_util import copy_tree
100
+ from shutil import copytree
101
+ copytree(str(slf), str(dest))
102
+ if verbose: print(f"🖨️ COPIED {'Content of ' if content else ''} {repr(slf)} ==> {repr(dest)}")
103
+ else: print(f"💥 Could NOT COPY. Not a file nor a path: {repr(slf)}.")
104
+ return dest if not orig else self
105
+ # ======================================= 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
+ def download(self, folder: OPLike = None, name: Optional[str]= None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> 'P':
148
+ import requests
149
+ 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').
150
+ assert response.status_code == 200, f"Download failed with status code {response.status_code}\n{response.text}"
151
+ if name is not None: f_name = name
152
+ else:
153
+ try:
154
+ f_name = response.headers['Content-Disposition'].split('filename=')[1].replace('"', '')
155
+ except (KeyError, IndexError):
156
+ f_name = validate_name(str(P(response.history[-1].url).name if len(response.history) > 0 else P(response.url).name))
157
+ dest_path = (P.home().joinpath("Downloads") if folder is None else P(folder)).joinpath(f_name)
158
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
159
+ return dest_path.write_bytes(response.content)
160
+ 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
+ res = P(res)
162
+ if inplace:
163
+ assert self.exists(), f"`inplace` flag is only relevant if the path exists. It doesn't {self}"
164
+ if operation == "rename":
165
+ if overwrite and res.exists(): res.delete(sure=True, verbose=verbose)
166
+ if not overwrite and res.exists():
167
+ if strict: raise FileExistsError(f"❌ RENAMING failed. File `{res}` already exists.")
168
+ else:
169
+ if verbose: print(f"⚠️ SKIPPED RENAMING {repr(self)} ➡️ {repr(res)} because FileExistsError and scrict=False policy.")
170
+ return self if orig else res
171
+ self.rename(res)
172
+ msg = msg or f"RENAMED {repr(self)} ➡️ {repr(res)}"
173
+ elif operation == "delete":
174
+ self.delete(sure=True, verbose=False)
175
+ __delayed_msg__ = f"DELETED 🗑️❌ {repr(self)}."
176
+ if verbose and msg != "":
177
+ try: print(msg) # emojie print error.
178
+ except UnicodeEncodeError: print("P._return warning: UnicodeEncodeError, could not print message.")
179
+ if verbose and __delayed_msg__ != "":
180
+ try: print(__delayed_msg__)
181
+ except UnicodeEncodeError: print("P._return warning: UnicodeEncodeError, could not print message.")
182
+ 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
+ def append(self, name: str = '', index: bool = False, suffix: Optional[str] = None, verbose: bool = True, **kwargs: Any) -> 'P':
189
+ """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
+ if index:
191
+ appended_name = f'''{name}_{len(self.parent.search(f"*{self.name.split('.')[0]}*"))}'''
192
+ return self.append(name=appended_name, index=False, verbose=verbose, suffix=suffix, **kwargs)
193
+ full_name = (name or ("_" + str(timestamp())))
194
+ full_suffix = suffix or ''.join(('bruh' + self).suffixes)
195
+ subpath = self.name.split('.')[0] + full_name + full_suffix
196
+ return self._return(self.parent.joinpath(subpath), operation="rename", verbose=verbose, **kwargs)
197
+ def with_name(self, name: str, verbose: bool = True, inplace: bool = False, overwrite: bool = False, **kwargs: Any):
198
+ 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
+ def __deepcopy__(self, *args: Any, **kwargs: Any) -> 'P':
206
+ _ = args, kwargs
207
+ return P(str(self))
208
+ def __getstate__(self) -> str: return str(self)
209
+ def __add__(self, other: PLike) -> 'P':
210
+ return self.parent.joinpath(self.name + str(other)) # used append and prepend if the addition wanted to be before suffix.
211
+ def __radd__(self, other: PLike) -> 'P':
212
+ return self.parent.joinpath(str(other) + self.name) # other + P and `other` doesn't know how to make this addition.
213
+ def __sub__(self, other: PLike) -> 'P':
214
+ res = P(str(self).replace(str(other), ""))
215
+ 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
+
217
+ 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
+ def collapseuser(self, strict: bool = True, placeholder: str = "~") -> 'P': # opposite of `expanduser` resolve is crucial to fix Windows cases insensitivty problem.
219
+ if strict: assert P.home() in self.expanduser().absolute().resolve(), ValueError(f"`{P.home()}` is not in the subpath of `{self}`")
220
+ if (str(self).startswith(placeholder) or P.home().as_posix() not in self.resolve().as_posix()): return self
221
+ 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
+ def __getitem__(self, slici: Union[int, list[int], slice]):
223
+ if isinstance(slici, list): return P(*[self[item] for item in slici])
224
+ elif isinstance(slici, int): return P(self.parts[slici])
225
+ return P(*self.parts[slici]) # must be a slice
226
+ def split(self, at: Optional[str] = None, index: Optional[int] = None, sep: Literal[-1, 0, 1] = 1, strict: bool = True):
227
+ if index is None and at is not None: # at is provided # ==================================== Splitting
228
+ if not strict: # behaves like split method of string
229
+ one, two = (items := str(self).split(sep=str(at)))[0], items[1]
230
+ one, two = P(one[:-1]) if one.endswith("/") else P(one), P(two[1:]) if two.startswith("/") else P(two)
231
+ else: # "strict": # raises an error if exact match is not found.
232
+ index = self.parts.index(str(at))
233
+ one, two = self[0:index], self[index + 1:] # both one and two do not include the split item.
234
+ elif index is not None and at is None: # index is provided
235
+ one, two = self[:index], P(*self.parts[index + 1:])
236
+ at = self.parts[index] # this is needed below.
237
+ else: raise ValueError("Either `index` or `at` can be provided. Both are not allowed simulatanesouly.")
238
+ if sep == 0: return one, two # neither of the portions get the sperator appended to it. # ================================ appending `at` to one of the portions
239
+ elif sep == 1: return one, P(at) / two # append it to right portion
240
+ elif sep == -1:
241
+ return one / at, two # append it to left portion.
242
+ else: raise ValueError(f"`sep` should take a value from the set [-1, 0, 1] but got {sep}")
243
+ def __repr__(self): # this is useful only for the console
244
+ if self.is_symlink():
245
+ try: target = self.resolve() # broken symolinks are funny, and almost always fail `resolve` method.
246
+ except Exception: target = "BROKEN LINK " + str(self) # avoid infinite recursions for broken links.
247
+ return "🔗 Symlink '" + str(self) + "' ==> " + (str(target) if target == self else str(target))
248
+ elif self.is_absolute(): return self._type() + " '" + str(self.clickable()) + "'" + (" | " + self.time(which="c").isoformat()[:-7].replace("T", " ") if self.exists() else "") + (f" | {self.size()} Mb" if self.is_file() else "")
249
+ elif "http" in str(self): return "🕸️ URL " + str(self.as_url_str())
250
+ else: return "📍 Relative " + "'" + str(self) + "'" # not much can be said about a relative path.
251
+ # def to_str(self) -> str: return str(self)
252
+ def size(self, units: Literal['b', 'kb', 'mb', 'gb'] = 'mb') -> float: # ===================================== File Specs ==========================================================================================
253
+ total_size = self.stat().st_size if self.is_file() else sum([item.stat().st_size for item in self.rglob("*") if item.is_file()])
254
+ tmp: int
255
+ match units:
256
+ case "b": tmp = 1024 ** 0
257
+ case "kb": tmp = 1024 ** 1
258
+ case "mb": tmp = 1024 ** 2
259
+ case "gb": tmp = 1024 ** 3
260
+ return round(number=total_size / tmp, ndigits=1)
261
+ def time(self, which: Literal["m", "c", "a"] = "m", **kwargs: Any):
262
+ """* `m`: last mofidication of content, i.e. the time it was created.
263
+ * `c`: last status change (its inode is changed, permissions, path, but not content)
264
+ * `a`: last access (read)
265
+ """
266
+ match which:
267
+ case "m": tmp = self.stat().st_mtime
268
+ case "a": tmp = self.stat().st_atime
269
+ case "c": tmp = self.stat().st_ctime
270
+ return datetime.fromtimestamp(tmp, **kwargs)
271
+
272
+ # ================================ String Nature management ====================================
273
+ def clickable(self, ) -> 'P': return self._return(res=P(self.expanduser().resolve().as_uri()), operation='Whack')
274
+ def as_url_str(self) -> 'str': return self.as_posix().replace("https:/", "https://").replace("http:/", "http://")
275
+ def as_zip_path(self):
276
+ import zipfile
277
+ res = self.expanduser().resolve()
278
+ return zipfile.Path(res) # .str.split(".zip") tmp=res[1]+(".zip" if len(res) > 2 else ""); root=res[0]+".zip", at=P(tmp).as_posix()) # TODO
279
+ # ========================== override =======================================
280
+ def __setitem__(self, key: Union['str', int, slice], value: PLike):
281
+ fullparts, new = list(self.parts), list(P(value).parts)
282
+ if type(key) is str:
283
+ idx = fullparts.index(key)
284
+ fullparts.remove(key)
285
+ fullparts = fullparts[:idx] + new + fullparts[idx + 1:]
286
+ elif type(key) is int: fullparts = fullparts[:key] + new + fullparts[key + 1:]
287
+ elif type(key) is slice: fullparts = fullparts[:(0 if key.start is None else key.start)] + new + fullparts[(len(fullparts) if key.stop is None else key.stop):]
288
+ self._str = str(P(*fullparts)) # pylint: disable=W0201 # similar attributes: # self._parts # self._pparts # self._cparts # self._cached_cparts
289
+
290
+ def _type(self):
291
+ if self.absolute():
292
+ if self.is_file(): return "📄"
293
+ elif self.is_dir(): return "📁"
294
+ return "👻NotExist"
295
+ return "📍Relative"
296
+ def write_text(self, data: str, encoding: str = 'utf-8', newline: Optional[str] = None) -> 'P':
297
+ self.parent.mkdir(parents=True, exist_ok=True)
298
+ super(P, self).write_text(data, encoding=encoding, newline=newline)
299
+ return self
300
+ def read_text(self, encoding: Optional[str] = 'utf-8') -> str: return super(P, self).read_text(encoding=encoding)
301
+ def write_bytes(self, data: bytes, overwrite: bool = False) -> 'P':
302
+ slf = self.expanduser().absolute()
303
+ if overwrite and slf.exists(): slf.delete(sure=True)
304
+ res = super(P, slf).write_bytes(data)
305
+ if res == 0: raise RuntimeError("Could not save file on disk.")
306
+ return self
307
+ # def touch(self, mode: int = 0o666, parents: bool = True, exist_ok: bool = True) -> 'P': # pylint: disable=W0237
308
+ # """Deprecated: rely on pathlib.Path.touch at call sites.
309
+ # Behavior was:
310
+ # - if parents: ensure parent directories exist
311
+ # - then call Path.touch(mode=mode, exist_ok=exist_ok)
312
+ # - return self
313
+ # Replace usages with:
314
+ # p.parent.mkdir(parents=True, exist_ok=True); p.touch(mode=..., exist_ok=...)
315
+ # """
316
+ # if parents: self.parent.mkdir(parents=parents, exist_ok=True)
317
+ # super(P, self).touch(mode=mode, exist_ok=exist_ok)
318
+ # return self
319
+
320
+ def symlink_to(self, target: PLike, verbose: bool = True, overwrite: bool = False, orig: bool = False, strict: bool = True): # pylint: disable=W0237
321
+ self.parent.mkdir(parents=True, exist_ok=True)
322
+ target_obj = P(target).expanduser().resolve()
323
+ if strict: assert target_obj.exists(), f"Target path `{target}` (aka `{target_obj}`) doesn't exist. This will create a broken link."
324
+ if overwrite and (self.is_symlink() or self.exists()): self.delete(sure=True, verbose=verbose)
325
+ from platform import system
326
+ from crocodile.meta import Terminal
327
+ if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
328
+ Terminal.run_as_admin(file=sys.executable, params=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"", wait=True)
329
+ else: super(P, self.expanduser()).symlink_to(str(target_obj))
330
+ return self._return(target_obj, operation='Whack', inplace=False, orig=orig, verbose=verbose, msg=f"LINKED {repr(self)} ➡️ {repr(target_obj)}")
331
+ def resolve(self, strict: bool = False):
332
+ try: return super(P, self).resolve(strict=strict)
333
+ except OSError: return self
334
+ # ======================================== Folder management =======================================
335
+ def search(self, pattern: str = '*', r: bool = False, files: bool = True, folders: bool = True, compressed: bool = False, dotfiles: bool = False, filters_total: Optional[list[Callable[[Any], bool]]] = None, not_in: Optional[list[str]] = None,
336
+ exts: Optional[list[str]] = None, win_order: bool = False) -> List['P']:
337
+ if isinstance(not_in, list):
338
+ filters_notin = [lambda x: all([str(a_not_in) not in str(x) for a_not_in in not_in])] # type: ignore
339
+ else: filters_notin = []
340
+ if isinstance(exts, list):
341
+ filters_extension = [lambda x: any([ext in x.name for ext in exts])] # type: ignore
342
+ else: filters_extension = []
343
+ filters_total = (filters_total or []) + filters_notin + filters_extension
344
+ if not files: filters_total.append(lambda x: x.is_dir())
345
+ if not folders: filters_total.append(lambda x: x.is_file())
346
+ slf = self.expanduser().resolve()
347
+ if ".zip" in str(slf) and compressed: # the root (self) is itself a zip archive (as opposed to some search results are zip archives)
348
+ import zipfile
349
+ import fnmatch
350
+ root = slf.as_zip_path()
351
+ if not r:
352
+ raw = List(root.iterdir())
353
+ else:
354
+ raw = List(zipfile.ZipFile(str(slf)).namelist()).apply(root.joinpath)
355
+ res1 = raw.filter(lambda zip_path: fnmatch.fnmatch(zip_path.at, pattern)) # type: ignore
356
+ return res1.filter(lambda x: (folders or x.is_file()) and (files or x.is_dir())) # type: ignore
357
+ elif dotfiles: raw = slf.glob(pattern) if not r else self.rglob(pattern)
358
+ else:
359
+ from glob import glob
360
+ if r:
361
+ raw = glob(str(slf / "**" / pattern), recursive=r)
362
+ else:
363
+ raw = glob(str(slf.joinpath(pattern))) # glob ignroes dot and hidden files
364
+ if ".zip" not in str(slf) and compressed:
365
+ filters_notin = [P(comp_file).search(pattern=pattern, r=r, files=files, folders=folders, compressed=True, dotfiles=dotfiles, filters_total=filters_total, not_in=not_in, win_order=win_order) for comp_file in self.search("*.zip", r=r)]
366
+ haha = List(filters_notin).reduce(func=lambda x, y: x + y)
367
+ raw = raw + haha # type: ignore
368
+ processed = []
369
+ for item in raw:
370
+ item_ = P(item)
371
+ if all([afilter(item_) for afilter in filters_total]):
372
+ processed.append(item_)
373
+ if not win_order: return List(processed)
374
+ import re
375
+ processed.sort(key=lambda x: [int(k) if k.isdigit() else k for k in re.split('([0-9]+)', string=x.stem)])
376
+ return List(processed)
377
+
378
+ # def create(self, parents: bool = True, exist_ok: bool = True, parents_only: bool = False) -> 'P':
379
+ # """Deprecated. Use Path.mkdir directly at the call site:
380
+ # - When creating a directory: self.mkdir(parents=True, exist_ok=True)
381
+ # - When ensuring parent exists: self.parent.mkdir(parents=True, exist_ok=True)
382
+ # This method used to:
383
+ # target_path = self.parent if parents_only else self
384
+ # target_path.mkdir(parents=parents, exist_ok=exist_ok)
385
+ # return self
386
+ # """
387
+ # target_path = self.parent if parents_only else self
388
+ # target_path.mkdir(parents=parents, exist_ok=exist_ok)
389
+ # return self
390
+
391
+ @staticmethod
392
+ def tmpdir(prefix: str = "") -> 'P':
393
+ return P.tmp(folder=rf"tmp_dirs/{prefix + ('_' if prefix != '' else '') + randstr()}")
394
+ @staticmethod
395
+ def tmpfile(name: Optional[str]= None, suffix: str = "", folder: OPLike = None, tstamp: bool = False, noun: bool = False) -> 'P':
396
+ name_concrete = name or randstr(noun=noun)
397
+ return P.tmp(file=name_concrete + "_" + randstr() + (("_" + str(timestamp())) if tstamp else "") + suffix, folder=folder or "tmp_files")
398
+ @staticmethod
399
+ def tmp(folder: OPLike = None, file: Optional[str] = None, root: str = "~/tmp_results") -> 'P':
400
+ base = P(root).expanduser().joinpath(folder or "").joinpath(file or "")
401
+ target_path = base.parent if file else base
402
+ target_path.mkdir(parents=True, exist_ok=True)
403
+ return base
404
+ # ====================================== Compression & Encryption ===========================================
405
+ def zip(self, path: OPLike = None, folder: OPLike = None, name: Optional[str]= None, arcname: Optional[str] = None, inplace: bool = False, verbose: bool = True,
406
+ content: bool = False, orig: bool = False, use_7z: bool = False, pwd: Optional[str] = None, mode: FILE_MODE = 'w', **kwargs: Any) -> 'P':
407
+ path_resolved, slf = self._resolve_path(folder, name, path, self.name).expanduser().resolve(), self.expanduser().resolve()
408
+ if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
409
+ path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
410
+ with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
411
+ else:
412
+ arcname_obj = P(arcname or slf.name)
413
+ if arcname_obj.name != slf.name: arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
414
+ if slf.is_file():
415
+ path_resolved = Compression.zip_file(ip_path=str(slf), op_path=str(path_resolved + ".zip" if path_resolved.suffix != ".zip" else path_resolved), arcname=str(arcname_obj), mode=mode, **kwargs)
416
+ else:
417
+ if content: root_dir, base_dir = slf, "."
418
+ else: root_dir, base_dir = slf.split(at=str(arcname_obj[0]), sep=1)[0], str(arcname_obj)
419
+ path_resolved = P(Compression.compress_folder(root_dir=str(root_dir), op_path=str(path_resolved), base_dir=base_dir, fmt='zip', **kwargs)) # TODO: see if this supports mode
420
+ return self._return(path_resolved, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"ZIPPED {repr(slf)} ==> {repr(path)}")
421
+ 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
+ pwd: Optional[str] = None, tmp: bool = False, pattern: Optional[str] = None, merge: bool = False) -> 'P':
423
+ assert merge is False, "I have not implemented this yet"
424
+ assert path is None, "I have not implemented this yet"
425
+ if tmp: return self.unzip(folder=P.tmp().joinpath("tmp_unzips").joinpath(randstr()), content=True).joinpath(self.stem)
426
+ slf = zipfile__ = self.expanduser().resolve()
427
+ if any(ztype in str(slf.parent) for ztype in (".zip", ".7z")): # path include a zip archive in the middle.
428
+ tmp__ = [item for item in (".zip", ".7z", "") if item in str(slf)]
429
+ ztype = tmp__[0]
430
+ if ztype == "": return slf
431
+ zipfile__, name__ = slf.split(at=str(List(slf.parts).filter(lambda x: ztype in x)[0]), sep=-1)
432
+ name = str(name__)
433
+ folder = (zipfile__.parent / zipfile__.stem) if folder is None else P(folder).expanduser().absolute().resolve().joinpath(zipfile__.stem)
434
+ folder = folder if not content else folder.parent
435
+ if slf.suffix == ".7z":
436
+ if overwrite: P(folder).delete(sure=True)
437
+ result = folder
438
+ import py7zr
439
+ with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
440
+ if pattern is not None:
441
+ import re
442
+ pat = re.compile(pattern)
443
+ archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
444
+ else: archive.extractall(path=folder)
445
+ else:
446
+ if overwrite:
447
+ 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
+ else:
449
+ import zipfile
450
+ List([x for x in zipfile.ZipFile(str(self)).namelist() if "/" not in x or (len(x.split('/')) == 2 and x.endswith("/"))]).apply(lambda item: P(folder).joinpath(name or "", item.replace("/", "")).delete(sure=True, verbose=True))
451
+ result = Compression.unzip(str(zipfile__), str(folder), None if name is None else P(name).as_posix())
452
+ assert isinstance(result, Path)
453
+ return self._return(P(result), inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNZIPPED {repr(zipfile__)} ==> {repr(result)}")
454
+ def untar(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> 'P':
455
+ op_path = self._resolve_path(folder, name, path, self.name.replace(".tar", "")).expanduser().resolve()
456
+ Compression.untar(str(self.expanduser().resolve()), op_path=str(op_path))
457
+ return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNTARRED {repr(self)} ==> {repr(op_path)}")
458
+ def ungz(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> 'P':
459
+ op_path = self._resolve_path(folder, name, path, self.name.replace(".gz", "")).expanduser().resolve()
460
+ Compression.ungz(str(self.expanduser().resolve()), op_path=str(op_path))
461
+ return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNGZED {repr(self)} ==> {repr(op_path)}")
462
+ def unxz(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> 'P':
463
+ op_path = self._resolve_path(folder, name, path, self.name.replace(".xz", "")).expanduser().resolve()
464
+ Compression.unxz(str(self.expanduser().resolve()), op_path=str(op_path))
465
+ return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNXZED {repr(self)} ==> {repr(op_path)}")
466
+ def unbz(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> 'P':
467
+ op_path = self._resolve_path(folder=folder, name=name, path=path, default_name=self.name.replace(".bz", "").replace(".tbz", ".tar")).expanduser().resolve()
468
+ Compression.unbz(str(self.expanduser().resolve()), op_path=str(op_path))
469
+ return self._return(op_path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"UNBZED {repr(self)} ==> {repr(op_path)}")
470
+ def decompress(self, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> 'P':
471
+ if ".tar.gz" in str(self) or ".tgz" in str(self):
472
+ # res = self.ungz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
473
+ return self.ungz(name=f"tmp_{randstr()}.tar", inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose) # this works for .tgz suffix as well as .tar.gz
474
+ elif ".gz" in str(self): res = self.ungz(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
475
+ elif ".tar.bz" in str(self) or "tbz" in str(self):
476
+ res = self.unbz(name=f"tmp_{randstr()}.tar", inplace=inplace)
477
+ return res.untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
478
+ elif ".tar.xz" in str(self):
479
+ # res = self.unxz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
480
+ res = self.unxz(inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
481
+ elif ".zip" in str(self): res = self.unzip(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
482
+ else: res = self
483
+ return res
484
+ def encrypt(self, key: Optional[bytes] = None, pwd: Optional[str] = None, folder: OPLike = None, name: Optional[str]= None, path: OPLike = None,
485
+ verbose: bool = True, suffix: str = ".enc", inplace: bool = False, orig: bool = False) -> 'P':
486
+ # 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"""
487
+ slf = self.expanduser().resolve()
488
+ path = self._resolve_path(folder, name, path, slf.name + suffix)
489
+ assert slf.is_file(), f"Cannot encrypt a directory. You might want to try `zip_n_encrypt`. {self}"
490
+ path.write_bytes(encrypt(msg=slf.read_bytes(), key=key, pwd=pwd))
491
+ return self._return(path, inplace=inplace, operation="delete", orig=orig, verbose=verbose, msg=f"🔒🔑 ENCRYPTED: {repr(slf)} ==> {repr(path)}.")
492
+ 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
+ slf = self.expanduser().resolve()
494
+ path = self._resolve_path(folder=folder, name=name, path=path, default_name=slf.name.replace(suffix, "") if suffix in slf.name else "decrypted_" + slf.name)
495
+ path.write_bytes(data=decrypt(token=slf.read_bytes(), key=key, pwd=pwd))
496
+ return self._return(path, operation="delete", verbose=verbose, msg=f"🔓🔑 DECRYPTED: {repr(slf)} ==> {repr(path)}.", inplace=inplace)
497
+ 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
+ return self.zip(inplace=inplace, verbose=verbose, content=content).encrypt(key=key, pwd=pwd, verbose=verbose, inplace=True) if not orig else self
499
+ def decrypt_n_unzip(self, key: Optional[bytes] = None, pwd: Optional[str] = None, inplace: bool = False, verbose: bool = True, orig: bool = False) -> 'P': return self.decrypt(key=key, pwd=pwd, verbose=verbose, inplace=inplace).unzip(folder=None, inplace=True, content=False) if not orig else self
500
+ def _resolve_path(self, folder: OPLike, name: Optional[str], path: OPLike, default_name: str, rel2it: bool = False) -> 'P':
501
+ """:param rel2it: `folder` or `path` are relative to `self` as opposed to cwd. This is used when resolving '../dir'"""
502
+ if path is not None:
503
+ path = P(self.joinpath(path).resolve() if rel2it else path).expanduser().resolve()
504
+ assert folder is None and name is None, "If `path` is passed, `folder` and `name` cannot be passed."
505
+ 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
+ return path
507
+ 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
+ return P(self.joinpath(folder).resolve() if rel2it else folder).expanduser().resolve() / name
509
+
510
+ def get_remote_path(self, root: Optional[str], os_specific: bool = False, rel2home: bool = True, strict: bool = True, obfuscate: bool = False) -> 'P':
511
+ import platform
512
+ tmp1: str = (platform.system().lower() if os_specific else 'generic_os')
513
+ if not rel2home: path = self
514
+ else:
515
+ try: path = self.rel2home()
516
+ except ValueError as ve:
517
+ if strict: raise ve
518
+ path = self
519
+ if obfuscate:
520
+ from crocodile.msc.obfuscater import obfuscate as obfuscate_func
521
+ name = obfuscate_func(seed=P.home().joinpath('dotfiles/creds/data/obfuscation_seed').read_text().rstrip(), data=path.name)
522
+ path = path.with_name(name=name)
523
+ if isinstance(root, str): # the following is to avoid the confusing behaviour of A.joinpath(B) if B is absolute.
524
+ part1 = path.parts[0]
525
+ if part1 == "/": sanitized_path = path[1:].as_posix()
526
+ else: sanitized_path = path.as_posix()
527
+ return P(root + "/" + tmp1 + "/" + sanitized_path)
528
+ return tmp1 / path
529
+ def to_cloud(self, cloud: str, remotepath: OPLike = None, zip: bool = False,encrypt: bool = False, # pylint: disable=W0621, W0622
530
+ key: Optional[bytes] = None, pwd: Optional[str] = None, rel2home: bool = False, strict: bool = True,
531
+ obfuscate: bool = False,
532
+ share: bool = False, verbose: bool = True, os_specific: bool = False, transfers: int = 10, root: Optional[str] = "myhome") -> 'P':
533
+ to_del = []
534
+ localpath = self.expanduser().absolute() if not self.exists() else self
535
+ if zip:
536
+ localpath = localpath.zip(inplace=False)
537
+ to_del.append(localpath)
538
+ if encrypt:
539
+ localpath = localpath.encrypt(key=key, pwd=pwd, inplace=False)
540
+ to_del.append(localpath)
541
+ if remotepath is None:
542
+ rp = localpath.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict, obfuscate=obfuscate) # if rel2home else (P(root) / localpath if root is not None else localpath)
543
+ else: rp = P(remotepath)
544
+ rclone_cmd = f"""rclone copyto '{localpath.as_posix()}' '{cloud}:{rp.as_posix()}' {'--progress' if verbose else ''} --transfers={transfers}"""
545
+ from crocodile.meta import Terminal
546
+ if verbose: print(f"{'⬆️'*5} UPLOADING with `{rclone_cmd}`")
547
+ shell_to_use = "powershell" if sys.platform == "win32" else "bash"
548
+ res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use).capture()
549
+ _ = [item.delete(sure=True) for item in to_del]
550
+ assert res.is_successful(strict_err=False, strict_returcode=True), res.print(capture=False, desc="Cloud Storage Operation")
551
+ if verbose: print(f"{'⬆️'*5} UPLOAD COMPLETED.")
552
+ if share:
553
+ if verbose: print("🔗 SHARING FILE")
554
+ shell_to_use = "powershell" if sys.platform == "win32" else "bash"
555
+ res = Terminal().run(f"""rclone link '{cloud}:{rp.as_posix()}'""", shell=shell_to_use).capture()
556
+ tmp = res.op2path(strict_err=False, strict_returncode=False)
557
+ if tmp is None:
558
+ res.print()
559
+ raise RuntimeError(f"💥 Could not get link for {self}.")
560
+ else:
561
+ res.print_if_unsuccessful(desc="Cloud Storage Operation", strict_err=True, strict_returncode=True)
562
+ return tmp
563
+ return self
564
+ def from_cloud(self, cloud: str, remotepath: OPLike = None, decrypt: bool = False, unzip: bool = False, # type: ignore # pylint: disable=W0621
565
+ key: Optional[bytes] = None, pwd: Optional[str] = None, rel2home: bool = False, os_specific: bool = False, strict: bool = True,
566
+ transfers: int = 10, root: Optional[str] = "myhome", verbose: bool = True, overwrite: bool = True, merge: bool = False,):
567
+ if remotepath is None:
568
+ remotepath = self.get_remote_path(root=root, os_specific=os_specific, rel2home=rel2home, strict=strict)
569
+ remotepath += ".zip" if unzip else ""
570
+ remotepath += ".enc" if decrypt else ""
571
+ else: remotepath = P(remotepath)
572
+ localpath = self.expanduser().absolute()
573
+ localpath += ".zip" if unzip else ""
574
+ localpath += ".enc" if decrypt else ""
575
+ rclone_cmd = f"""rclone copyto '{cloud}:{remotepath.as_posix()}' '{localpath.as_posix()}' {'--progress' if verbose else ''} --transfers={transfers}"""
576
+ from crocodile.meta import Terminal
577
+ if verbose: print(f"{'⬇️' * 5} DOWNLOADING with `{rclone_cmd}`")
578
+ shell_to_use = "powershell" if sys.platform == "win32" else "bash"
579
+ res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)
580
+ success = res.is_successful(strict_err=False, strict_returcode=True)
581
+ if not success:
582
+ res.print(capture=False, desc="Cloud Storage Operation")
583
+ return None
584
+ if decrypt: localpath = localpath.decrypt(key=key, pwd=pwd, inplace=True)
585
+ if unzip: localpath = localpath.unzip(inplace=True, verbose=True, overwrite=overwrite, content=True, merge=merge)
586
+ return localpath
587
+ def sync_to_cloud(self, cloud: str, sync_up: bool = False, sync_down: bool = False, os_specific: bool = False, rel2home: bool = True, transfers: int = 10, delete: bool = False, root: Optional[str] = "myhome", verbose: bool = True):
588
+ tmp_path_obj = self.expanduser().absolute()
589
+ tmp_path_obj.parent.mkdir(parents=True, exist_ok=True)
590
+ tmp1, tmp2 = tmp_path_obj.as_posix(), self.get_remote_path(root=root, os_specific=os_specific).as_posix()
591
+ source, target = (tmp1, f"{cloud}:{tmp2 if rel2home else tmp1}") if sync_up else (f"{cloud}:{tmp2 if rel2home else tmp1}", tmp1) # in bisync direction is irrelavent.
592
+ if not sync_down and not sync_up:
593
+ _ = print(f"SYNCING 🔄️ {source} {'<>' * 7} {target}`") if verbose else None
594
+ rclone_cmd = f"""rclone bisync '{source}' '{target}' --resync --remove-empty-dirs """
595
+ else:
596
+ print(f"SYNCING 🔄️ {source} {'>' * 15} {target}`")
597
+ rclone_cmd = f"""rclone sync '{source}' '{target}' """
598
+ rclone_cmd += f" --progress --transfers={transfers} --verbose"
599
+ rclone_cmd += (" --delete-during" if delete else "")
600
+ from crocodile.meta import Terminal
601
+ if verbose : print(rclone_cmd)
602
+ shell_to_use = "powershell" if sys.platform == "win32" else "bash"
603
+ res = Terminal(stdout=None if verbose else subprocess.PIPE).run(rclone_cmd, shell=shell_to_use)
604
+ success = res.is_successful(strict_err=False, strict_returcode=True)
605
+ if not success:
606
+ res.print(capture=False, desc="Cloud Storage Operation")
607
+ return None
608
+ return self