machineconfig 1.5__py3-none-any.whl → 1.8__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 (155) hide show
  1. machineconfig/__init__.py +8 -5
  2. machineconfig/jobs/python/check_installations.py +173 -163
  3. machineconfig/jobs/python/checkout_version.py +117 -0
  4. machineconfig/jobs/python/create_bootable_media.py +14 -14
  5. machineconfig/jobs/python/create_zellij_template.py +59 -56
  6. machineconfig/jobs/python/python_cargo_build_share.py +50 -45
  7. machineconfig/jobs/python/python_ve_symlink.py +20 -18
  8. machineconfig/jobs/python/tasks.py +4 -0
  9. machineconfig/jobs/script_installer/azure_data_studio.py +22 -0
  10. machineconfig/jobs/script_installer/bypass_paywall.py +23 -0
  11. machineconfig/jobs/script_installer/code.py +34 -0
  12. machineconfig/jobs/script_installer/docker_desktop.py +41 -0
  13. machineconfig/jobs/script_installer/ngrok.py +29 -0
  14. machineconfig/jobs/{python_linux_installers → script_installer}/skim.py +21 -19
  15. machineconfig/jobs/script_installer/wezterm.py +34 -0
  16. machineconfig/profile/create.py +107 -200
  17. machineconfig/profile/shell.py +127 -0
  18. machineconfig/scripts/__init__.py +6 -6
  19. machineconfig/scripts/python/cloud_copy.py +93 -0
  20. machineconfig/scripts/python/cloud_manager.py +38 -0
  21. machineconfig/scripts/python/cloud_mount.py +115 -52
  22. machineconfig/scripts/python/cloud_repo_sync.py +154 -114
  23. machineconfig/scripts/python/cloud_sync.py +261 -79
  24. machineconfig/scripts/python/croshell.py +151 -0
  25. machineconfig/scripts/python/devops.py +119 -87
  26. machineconfig/scripts/python/devops_add_identity.py +27 -23
  27. machineconfig/scripts/python/devops_add_ssh_key.py +70 -55
  28. machineconfig/scripts/python/devops_backup_retrieve.py +52 -46
  29. machineconfig/scripts/python/devops_devapps_install.py +120 -91
  30. machineconfig/scripts/python/devops_update_repos.py +82 -68
  31. machineconfig/scripts/python/dotfile.py +42 -38
  32. machineconfig/scripts/python/fire_jobs.py +351 -98
  33. machineconfig/scripts/python/ftpx.py +82 -0
  34. machineconfig/scripts/python/mount_nfs.py +54 -3
  35. machineconfig/scripts/python/mount_nw_drive.py +31 -0
  36. machineconfig/scripts/python/mount_ssh.py +44 -20
  37. machineconfig/scripts/python/onetimeshare.py +60 -51
  38. machineconfig/scripts/python/pomodoro.py +41 -37
  39. machineconfig/scripts/python/repos.py +195 -128
  40. machineconfig/scripts/python/scheduler.py +52 -0
  41. machineconfig/scripts/python/snapshot.py +21 -21
  42. machineconfig/scripts/python/start_slidev.py +104 -0
  43. machineconfig/scripts/python/start_terminals.py +97 -0
  44. machineconfig/scripts/python/wifi_conn.py +90 -71
  45. machineconfig/scripts/python/{transfer_wsl_win.py → wsl_windows_transfer.py} +47 -39
  46. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +44 -48
  47. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +136 -130
  48. machineconfig/utils/installer.py +251 -0
  49. machineconfig/utils/procs.py +114 -64
  50. machineconfig/utils/scheduling.py +188 -0
  51. machineconfig/utils/utils.py +353 -249
  52. machineconfig/utils/ve.py +222 -0
  53. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/METADATA +140 -110
  54. machineconfig-1.8.dist-info/RECORD +70 -0
  55. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/WHEEL +1 -1
  56. machineconfig/jobs/python/python_linux_installers_all.py +0 -73
  57. machineconfig/jobs/python/python_ve_installer.py +0 -73
  58. machineconfig/jobs/python/python_windows_installers_all.py +0 -23
  59. machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -15
  60. machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -32
  61. machineconfig/jobs/python_generic_installers/archive/vtm.py +0 -25
  62. machineconfig/jobs/python_generic_installers/broot.py +0 -39
  63. machineconfig/jobs/python_generic_installers/browsh.py +0 -25
  64. machineconfig/jobs/python_generic_installers/bw.py +0 -26
  65. machineconfig/jobs/python_generic_installers/chatgpt.py +0 -24
  66. machineconfig/jobs/python_generic_installers/cpufetch.py +0 -23
  67. machineconfig/jobs/python_generic_installers/delta.py +0 -59
  68. machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
  69. machineconfig/jobs/python_generic_installers/dev/autogpt.py +0 -28
  70. machineconfig/jobs/python_generic_installers/dev/bw.py +0 -29
  71. machineconfig/jobs/python_generic_installers/dev/evcxr.py +0 -22
  72. machineconfig/jobs/python_generic_installers/dev/kondo.py +0 -21
  73. machineconfig/jobs/python_generic_installers/dev/lvim.py +0 -60
  74. machineconfig/jobs/python_generic_installers/dev/ngrok.py +0 -35
  75. machineconfig/jobs/python_generic_installers/dev/opencommit.py +0 -21
  76. machineconfig/jobs/python_generic_installers/dev/qrcp.py +0 -25
  77. machineconfig/jobs/python_generic_installers/dev/qrscan.py +0 -16
  78. machineconfig/jobs/python_generic_installers/dev/rust-analyzer.py +0 -24
  79. machineconfig/jobs/python_generic_installers/dev/termscp.py +0 -23
  80. machineconfig/jobs/python_generic_installers/dev/tldr.py +0 -25
  81. machineconfig/jobs/python_generic_installers/dev/tokei.py +0 -24
  82. machineconfig/jobs/python_generic_installers/diskonaut.py +0 -26
  83. machineconfig/jobs/python_generic_installers/dua.py +0 -21
  84. machineconfig/jobs/python_generic_installers/evcxr.py +0 -21
  85. machineconfig/jobs/python_generic_installers/gitui.py +0 -23
  86. machineconfig/jobs/python_generic_installers/gopass.py +0 -19
  87. machineconfig/jobs/python_generic_installers/helix.py +0 -45
  88. machineconfig/jobs/python_generic_installers/kondo.py +0 -20
  89. machineconfig/jobs/python_generic_installers/lf.py +0 -25
  90. machineconfig/jobs/python_generic_installers/lvim.py +0 -34
  91. machineconfig/jobs/python_generic_installers/mprocs.py +0 -20
  92. machineconfig/jobs/python_generic_installers/navi.py +0 -20
  93. machineconfig/jobs/python_generic_installers/ots.py +0 -26
  94. machineconfig/jobs/python_generic_installers/ouch.py +0 -25
  95. machineconfig/jobs/python_generic_installers/pomodoro.py +0 -19
  96. machineconfig/jobs/python_generic_installers/procs.py +0 -20
  97. machineconfig/jobs/python_generic_installers/qrcp.py +0 -22
  98. machineconfig/jobs/python_generic_installers/qrscan.py +0 -14
  99. machineconfig/jobs/python_generic_installers/rclone.py +0 -21
  100. machineconfig/jobs/python_generic_installers/rust-analyzer.py +0 -24
  101. machineconfig/jobs/python_generic_installers/tere.py +0 -26
  102. machineconfig/jobs/python_generic_installers/termscp.py +0 -23
  103. machineconfig/jobs/python_generic_installers/tldr.py +0 -24
  104. machineconfig/jobs/python_generic_installers/tokei.py +0 -21
  105. machineconfig/jobs/python_generic_installers/vtm.py +0 -26
  106. machineconfig/jobs/python_generic_installers/watchexec.py +0 -21
  107. machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
  108. machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -18
  109. machineconfig/jobs/python_linux_installers/bandwhich.py +0 -11
  110. machineconfig/jobs/python_linux_installers/bottom.py +0 -17
  111. machineconfig/jobs/python_linux_installers/btop.py +0 -17
  112. machineconfig/jobs/python_linux_installers/dev/bandwhich.py +0 -13
  113. machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
  114. machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -21
  115. machineconfig/jobs/python_linux_installers/gotty.py +0 -16
  116. machineconfig/jobs/python_linux_installers/joshuto.py +0 -28
  117. machineconfig/jobs/python_linux_installers/mcfly.py +0 -12
  118. machineconfig/jobs/python_linux_installers/nnn.py +0 -18
  119. machineconfig/jobs/python_linux_installers/topgrade.py +0 -15
  120. machineconfig/jobs/python_linux_installers/viu.py +0 -19
  121. machineconfig/jobs/python_linux_installers/xplr.py +0 -22
  122. machineconfig/jobs/python_linux_installers/zellij.py +0 -31
  123. machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -20
  124. machineconfig/jobs/python_windows_installers/bat.py +0 -16
  125. machineconfig/jobs/python_windows_installers/boxes.py +0 -19
  126. machineconfig/jobs/python_windows_installers/bypass_paywall.py +0 -18
  127. machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -21
  128. machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -20
  129. machineconfig/jobs/python_windows_installers/fd.py +0 -17
  130. machineconfig/jobs/python_windows_installers/fzf.py +0 -19
  131. machineconfig/jobs/python_windows_installers/obs_background_removal_plugin.py +0 -19
  132. machineconfig/jobs/python_windows_installers/rg.py +0 -15
  133. machineconfig/jobs/python_windows_installers/ugrep.py +0 -14
  134. machineconfig/jobs/python_windows_installers/zoomit.py +0 -20
  135. machineconfig/jobs/python_windows_installers/zoxide.py +0 -20
  136. machineconfig/profile/fix_shell_profiles.py +0 -8
  137. machineconfig/scripts/python/archive/__init__.py +0 -0
  138. machineconfig/scripts/python/archive/bu_gdrive_rx.py +0 -41
  139. machineconfig/scripts/python/archive/bu_gdrive_sx.py +0 -40
  140. machineconfig/scripts/python/archive/bu_onedrive_rx.py +0 -59
  141. machineconfig/scripts/python/archive/bu_onedrive_sx.py +0 -45
  142. machineconfig/scripts/python/chatgpt.py +0 -28
  143. machineconfig/scripts/python/choose_ohmybash_theme.py +0 -25
  144. machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -40
  145. machineconfig/scripts/python/cloud_rx.py +0 -42
  146. machineconfig/scripts/python/cloud_sx.py +0 -40
  147. machineconfig/scripts/python/ftprx.py +0 -37
  148. machineconfig/scripts/python/ftpsx.py +0 -36
  149. machineconfig/scripts/python/im2text.py +0 -15
  150. machineconfig/scripts/python/tmate_conn.py +0 -28
  151. machineconfig/scripts/python/tmate_start.py +0 -31
  152. machineconfig/utils/to_exe.py +0 -7
  153. machineconfig-1.5.dist-info/RECORD +0 -147
  154. /machineconfig/jobs/{python_generic_installers/archive → script_installer}/__init__.py +0 -0
  155. {machineconfig-1.5.dist-info → machineconfig-1.8.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,44 @@
1
-
2
- import crocodile.toolbox as tb
3
- from platform import system
4
-
5
-
6
- def main():
7
- if system() == "Linux":
8
- txt = fr"""
9
- net use Y: \\sshfs\aalsaf01@sshfs\229234!9546
10
- """
11
- elif system() == "Windows":
12
- txt = fr"""
13
- sshfs alex@:/media/dbhdd /media/dbhdd
14
- fusermount -u /mnt/dbhdd
15
- """
16
- else: raise ValueError(f"Not implemented for this system {system()}")
17
-
18
-
19
- if __name__ == '__main__':
20
- pass
1
+
2
+ """Mount a remote SSHFS share on a local directory
3
+ """
4
+
5
+
6
+ from platform import system
7
+ from crocodile.meta import SSH, Terminal
8
+ from crocodile.file_management import P
9
+
10
+ from machineconfig.utils.utils import PROGRAM_PATH, choose_ssh_host
11
+
12
+
13
+ def main():
14
+
15
+ print(f"Mounting SSHFS ... ")
16
+ share_info = input("share path? (e.g. user@host:/path) [press enter for interactive choice] = ")
17
+ if share_info == "":
18
+ tmp = choose_ssh_host(multi=False)
19
+ assert isinstance(tmp, str)
20
+ ssh = SSH(host=tmp)
21
+ share_info = f"{ssh.username}@{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_ssh"
22
+ else:
23
+ ssh = SSH(share_info.split(":")[0])
24
+ print(Terminal().run("net use", shell="powershell").op)
25
+ driver_letter = input(r"Choose driver letter (e.g. Z:\) (avoid the ones already used) : ") or "Z:\\"
26
+
27
+ mount_point = input(f"Enter the mount point directory (ex: /mnt/network) [default: ~/data/mount_ssh/{ssh.hostname}]: ")
28
+ if mount_point == "": mount_point = P.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
29
+
30
+ if system() == "Linux":
31
+ txt = fr"""
32
+ sshfs alex@:/media/dbhdd /media/dbhdd\
33
+ """
34
+ elif system() == "Windows":
35
+ txt = fr"""
36
+ net use {driver_letter} {share_info}
37
+ fusermount -u /mnt/dbhdd
38
+ """
39
+ else: raise ValueError(f"Not implemented for this system {system()}")
40
+ PROGRAM_PATH.write_text(txt)
41
+
42
+
43
+ if __name__ == '__main__':
44
+ pass
@@ -1,51 +1,60 @@
1
-
2
- # as per https://github.com/Luzifer/ots
3
- # this script is pure python and connects to server to create a secret
4
- # or use ots executable to do this job
5
-
6
-
7
- import requests
8
- import crocodile.file_management as fm
9
-
10
-
11
- from Crypto.Cipher import AES
12
- from Crypto.Hash import MD5
13
- import base64
14
-
15
- def encrypt(key, pwd):
16
- pwd = pwd.encode("utf-8")
17
- hash_object = MD5.new(key.encode("utf-8"))
18
- key = hash_object.digest()
19
- iv = b'\x00' * 16
20
- cipher = AES.new(key, AES.MODE_CBC, iv)
21
- pad = 16 - (len(pwd) % 16)
22
- pwd += bytes([pad] * pad)
23
- encrypted_password = cipher.encrypt(pwd)
24
- return base64.b64encode(encrypted_password).decode("utf-8")
25
-
26
-
27
- secret = input("Secret: ")
28
- password = input("Password: ") or None
29
-
30
-
31
- if password is not None: encoded_secret = encrypt(password, secret)
32
- else: encoded_secret = secret
33
-
34
-
35
- url = "https://ots.fyi/api/create"
36
-
37
- payload = {"secret": encoded_secret}
38
- headers = {'Content-Type': 'application/json'}
39
-
40
- response = requests.post(url, json=payload, headers=headers)
41
-
42
- if response.status_code == 201:
43
- res = response.json()
44
- print(res)
45
- assert res["success"] is True, "Request have failed"
46
- share_url = fm.P(f"https://ots.fyi/#{res['secret_id']}") + (f"|{password}" if password is not None else "")
47
- print(repr(share_url))
48
- else:
49
- print("Request failed")
50
- raise RuntimeError(response.text)
51
-
1
+
2
+ """Share
3
+ """
4
+
5
+ # as per https://github.com/Luzifer/ots
6
+ # this script is pure python and connects to server to create a secret
7
+ # or use ots executable to do this job
8
+
9
+
10
+ import requests
11
+ import crocodile.file_management as fm
12
+ from crocodile.core import install_n_import
13
+ import base64
14
+ from typing import Optional
15
+
16
+
17
+ def encrypt(key: str, pwd: str):
18
+ install_n_import(library="Crypto.Cipher", package="pycryptodome")
19
+ # from Crypto.Cipher import AES
20
+ # from Crypto.Hash import MD5
21
+ AES = __import__("Crypto", fromlist=["Cipher"]).Cipher.AES
22
+ MD5 = __import__("Crypto", fromlist=["Hash"]).Hash.MD5
23
+ pwd_enc = pwd.encode("utf-8")
24
+ hash_object = MD5.new(key.encode("utf-8"))
25
+ key_digest = hash_object.digest()
26
+ iv = b'\x00' * 16
27
+ cipher = AES.new(key_digest, AES.MODE_CBC, iv)
28
+ pad = 16 - (len(pwd_enc) % 16)
29
+ pwd_enc += bytes([pad] * pad)
30
+ encrypted_password = cipher.encrypt(pwd_enc)
31
+ return base64.b64encode(encrypted_password).decode("utf-8")
32
+
33
+
34
+ def share(secret: str, password: Optional[str]):
35
+ if password is not None: encoded_secret = encrypt(password, secret)
36
+ else: encoded_secret = secret
37
+
38
+ url = "https://ots.fyi/api/create"
39
+
40
+ payload = {"secret": encoded_secret}
41
+ headers = {'Content-Type': 'application/json'}
42
+
43
+ response = requests.post(url, json=payload, headers=headers, timeout=10)
44
+
45
+ if response.status_code == 201:
46
+ res = response.json()
47
+ print(res)
48
+ assert res["success"] is True, "Request have failed"
49
+ share_url = fm.P(f"https://ots.fyi/#{res['secret_id']}") + (f"|{password}" if password is not None else "")
50
+ print(repr(share_url))
51
+ return share_url
52
+ else:
53
+ print("Request failed")
54
+ raise RuntimeError(response.text)
55
+
56
+
57
+ if __name__ == "__main__":
58
+ sc = input("Secret: ")
59
+ pwdd = input("Password: ") or None
60
+ share(secret=sc, password=pwdd)
@@ -1,37 +1,41 @@
1
-
2
- from crocodile.toolbox import Log, install_n_import, Scheduler, P
3
- import time
4
- from datetime import datetime
5
-
6
-
7
- def pomodoro(work=25, rest=5, repeats=4):
8
- logger = Log(name="pomodoro", file=False, stream=True)
9
- def loop(sched):
10
- speak("Alright, time to start working..."); start = datetime.now(); _ = sched
11
- while (diff := work - ((datetime.now() - start).seconds / 60)) > 0: logger.debug(f"Keep working. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
12
- speak("Now, its time to take a break."); start = datetime.now()
13
- while (diff := rest - ((datetime.now() - start).seconds / 60)) > 0: logger.critical(f"Keep Resting. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
14
- def speak(txt):
15
- install_n_import("gtts").gTTS(txt, lang='en', tld='com.au').save(tmp := P.tmpfile(suffix=".mp3")); time.sleep(0.5)
16
- pyglet = install_n_import("pyglet"); pyglet.resource.path = [tmp.parent.str]; pyglet.resource.reindex(); pyglet.resource.media(tmp.name).play()
17
- def beep(duration=1, frequency=3000):
18
- try: import winsound
19
- except ImportError: __import__("os").system('beep -f %s -l %s' % (frequency, 1000 * duration))
20
- else: winsound.Beep(frequency, 1000 * duration)
21
- return Scheduler(routine=loop, max_cycles=repeats, logger=logger, wait="0.1m").run()
22
-
23
-
24
- # def main():
25
- # parser = argparse.ArgumentParser(description='FTP client')
26
- #
27
- # parser.add_argument("machine", help=f"machine ssh address", default="")
28
- # parser.add_argument("file", help="file/folder path.", default="")
29
- # # FLAGS
30
- # parser.add_argument("--recursive", "-r", help="Send recursively.", action="store_true") # default is False
31
- # parser.add_argument("--zipFirst", "-z", help="Zip before sending.", action="store_true") # default is False
32
- #
33
- # args = parser.parse_args()
34
-
35
-
36
- if __name__ == '__main__':
37
- pomodoro()
1
+
2
+ """Pomodoro
3
+ """
4
+
5
+ from crocodile.toolbox import Log, install_n_import, Scheduler, P
6
+ import time
7
+ from datetime import datetime
8
+
9
+
10
+ def pomodoro(work: int = 25, rest: int = 5, repeats: int = 4):
11
+ logger = Log(name="pomodoro", file=False, stream=True)
12
+ def loop(sched: Scheduler):
13
+ speak("Alright, time to start working..."); start = datetime.now(); _ = sched
14
+ while (diff := work - ((datetime.now() - start).seconds / 60)) > 0: logger.debug(f"Keep working. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
15
+ speak("Now, its time to take a break."); start = datetime.now()
16
+ while (diff := rest - ((datetime.now() - start).seconds / 60)) > 0: logger.critical(f"Keep Resting. Time Left: {round(diff)} minutes"); time.sleep(60 * 1)
17
+ def speak(txt: str):
18
+ install_n_import("gtts").gTTS(txt, lang='en', tld='com.au').save(tmp := P.tmpfile(suffix=".mp3")); time.sleep(0.5)
19
+ pyglet = install_n_import("pyglet"); pyglet.resource.path = [tmp.parent.str]; pyglet.resource.reindex(); pyglet.resource.media(tmp.name).play()
20
+ def beep(duration: int = 1, frequency: int = 3000):
21
+ try: import winsound
22
+ except ImportError: __import__("os").system(f'beep -f {frequency} -l {1000 * duration}')
23
+ else: winsound.Beep(frequency, 1000 * duration) # type: ignore
24
+ _ = beep
25
+ return Scheduler(routine=loop, max_cycles=repeats, logger=logger, wait="0.1m").run()
26
+
27
+
28
+ # def main():
29
+ # parser = argparse.ArgumentParser(description='FTP client')
30
+ #
31
+ # parser.add_argument("machine", help=f"machine ssh address", default="")
32
+ # parser.add_argument("file", help="file/folder path.", default="")
33
+ # # FLAGS
34
+ # parser.add_argument("--recursive", "-r", help="Send recursively.", action="store_true") # default is False
35
+ # parser.add_argument("--zipFirst", "-z", help="Zip before sending.", action="store_true") # default is False
36
+ #
37
+ # args = parser.parse_args()
38
+
39
+
40
+ if __name__ == '__main__':
41
+ pomodoro()
@@ -1,128 +1,195 @@
1
-
2
- import crocodile.toolbox as tb
3
- import argparse
4
- from machineconfig.utils.utils import write_shell_script, CONFIG_PATH
5
- from rich import print
6
- # from dataclasses import dataclass
7
- from enum import Enum
8
- # tm = tb.Terminal()
9
-
10
-
11
- class GitAction(Enum):
12
- commit = "commit"
13
- push = "push"
14
- pull = "pull"
15
-
16
-
17
- def git_action(path: tb.P, action: GitAction, mess: str or None = None, r=False) -> str:
18
- import git
19
- try:
20
- repo = git.Repo(str(path), search_parent_directories=False)
21
- except git.exc.InvalidGitRepositoryError:
22
- print(f"Skipping {path} because it is not a git repository.")
23
- if r:
24
- prgs = [git_action(path=sub_path, action=action, mess=mess, r=r) for sub_path in path.search()]
25
- return "\n".join(prgs)
26
- else: return "\necho 'skipped because not a git repo'\n\n"
27
-
28
- program = f'''
29
- echo '>>>>>>>>> {action}'
30
- cd '{path}'
31
- '''
32
- if action == GitAction.commit:
33
- if mess is None: mess = "auto_commit_" + tb.randstr()
34
- program += f'''
35
- git add .; git commit -am "{mess}"
36
- '''
37
- if action == GitAction.push or action == GitAction.pull:
38
- # remotes = tm.run(f"cd {path}; git remote", shell="powershell").op.split("\n")
39
- action_name = "pull" if action == GitAction.pull else "push"
40
- cmds = [f'echo "pulling from {remote.url}" ; git {action_name} {remote.name} {repo.active_branch.name}' for remote in repo.remotes]
41
- program += '\n' + '\n'.join(cmds) + '\n'
42
- program = program + f'''
43
- echo ""; echo ""
44
- '''
45
- return program
46
-
47
-
48
- def main():
49
- parser = argparse.ArgumentParser(description='REPO MANAGER')
50
- # POSITIONAL
51
- parser.add_argument("directory", help="folder containing repos.", default="")
52
- # FLAGS
53
- parser.add_argument("--push", help=f"push", action="store_true")
54
- parser.add_argument("--pull", help=f"pull", action="store_true")
55
- parser.add_argument("--commit", help=f"commit", action="store_true")
56
- parser.add_argument("--all", help=f"pull, commit and push", action="store_true")
57
- parser.add_argument("--record", help=f"record respos", action="store_true")
58
- parser.add_argument("--clone", help=f"clone repos from record", action="store_true")
59
- parser.add_argument("--recursive", "-r", help=f"recursive flag", action="store_true")
60
- # OPTIONAL
61
- parser.add_argument("--cloud", "-c", help=f"cloud", default=None)
62
-
63
- args = parser.parse_args()
64
- if args.directory == "": path = tb.P.home().joinpath("code")
65
- else: path = tb.P(args.directory).expanduser().absolute()
66
- _ = tb.install_n_import("git", "gitpython")
67
-
68
- program = ""
69
- if args.record:
70
- res = record_repos(path=path)
71
- print(f"Recorded repositories:\n", res)
72
- save_path = tb.Save.pickle(obj=res, path=CONFIG_PATH.joinpath("repos").joinpath(path.rel2home()).joinpath("repos.pkl"))
73
- print(f"Result pickled at {tb.P(save_path)}")
74
- if args.cloud is not None: tb.P(save_path).to_cloud(rel2home=True, cloud=args.cloud)
75
- program += f"""\necho '>>>>>>>>> Finished Recording'\n"""
76
- elif args.clone:
77
- program += f"""\necho '>>>>>>>>> Cloning Repos'\n"""
78
- if not path.exists(): # user didn't pass absolute path to pickle file, but rather expected it to be in the default save location
79
- path = CONFIG_PATH.joinpath("repos").joinpath(path.rel2home()).joinpath("repos.pkl")
80
- assert (path.exists() and path.stem == 'repos.pkl') or args.cloud is not None, f"Path {path} does not exist and cloud was not passed. You can't clone without one of them."
81
- program += install_repos(path=path, cloud=args.cloud)
82
- elif args.all or args.commit or args.pull or args.push:
83
- for a_path in path.search("*"):
84
- program += f"""echo "{("Handling " + str(a_path)).center(80, "-")}" """
85
- if args.pull or args.all: program += git_action(path=a_path, action=GitAction.pull, r=args.recursive)
86
- if args.commit or args.all: program += git_action(a_path, action=GitAction.commit, r=args.recursive)
87
- if args.push or args.all: program += git_action(a_path, action=GitAction.push, r=args.recursive)
88
- else: program = "echo 'no action specified, try to pass --push, --pull, --commit or --all'"
89
- write_shell_script(program, "Script to update repos")
90
-
91
-
92
- def record_repos(path, r=True) -> list[dict]:
93
- import git
94
- path = tb.P(path).expanduser().absolute()
95
- if path.is_file(): return []
96
- search_res = path.search("*", files=False)
97
- res = []
98
- for a_search_res in search_res:
99
- if a_search_res.joinpath(".git").exists():
100
- # get list of remotes using git python
101
- repo = git.Repo(a_search_res)
102
- remotes = {remote.name: remote.url for remote in repo.remotes}
103
- res.append({"parent_dir": a_search_res.parent.collapseuser().as_posix(), "remotes": remotes})
104
- else:
105
- if r: res += record_repos(a_search_res, r=r)
106
- return res
107
-
108
-
109
- def install_repos(path=None, cloud=None):
110
- program = ""
111
- if cloud is not None:
112
- path = path.from_cloud(rel2home=True, cloud=cloud)
113
- else:
114
- path = tb.P(path).expanduser().absolute()
115
- repos = tb.Read.pickle(path)
116
- for repo in repos:
117
- for idx, (remote_name, remote_url) in enumerate(repo["remotes"].items()):
118
- parent_dir = tb.P(repo["parent_dir"]).expanduser().absolute().create()
119
- if idx == 0:
120
- program += f"\ncd {parent_dir}; git clone {remote_url} --origin {remote_name}\n"
121
- else:
122
- program += f"\ncd {parent_dir.as_posix()}/{tb.P(remote_url)[-1].stem}; git remote add {remote_name} {remote_url}\n"
123
- print(program)
124
- return program
125
-
126
-
127
- if __name__ == '__main__':
128
- main()
1
+
2
+ """Repos
3
+ """
4
+
5
+ from rich import print as pprint
6
+ from machineconfig.utils.utils import write_shell_script, CONFIG_PATH, DEFAULTS_PATH
7
+ from crocodile.file_management import P, install_n_import, Read, Save
8
+ from crocodile.core import randstr
9
+ import argparse
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+ from typing import Optional, Any
13
+ # tm = Terminal()
14
+
15
+
16
+ class GitAction(Enum):
17
+ commit = "commit"
18
+ push = "push"
19
+ pull = "pull"
20
+
21
+
22
+ @dataclass
23
+ class RepoRecord:
24
+ name: str
25
+ parent_dir: str
26
+ remotes: list[str]
27
+ version: dict[str, str]
28
+
29
+
30
+ def git_action(path: P, action: GitAction, mess: Optional[str] = None, r: bool = False) -> str:
31
+ from git.exc import InvalidGitRepositoryError
32
+ from git.repo import Repo
33
+ try:
34
+ repo = Repo(str(path), search_parent_directories=False)
35
+ except InvalidGitRepositoryError:
36
+ pprint(f"Skipping {path} because it is not a git repository.")
37
+ if r:
38
+ prgs = [git_action(path=sub_path, action=action, mess=mess, r=r) for sub_path in path.search()]
39
+ return "\n".join(prgs)
40
+ else: return "\necho 'skipped because not a git repo'\n\n"
41
+
42
+ program = f'''
43
+ echo '>>>>>>>>> {action}'
44
+ cd '{path}'
45
+ '''
46
+ if action == GitAction.commit:
47
+ if mess is None: mess = "auto_commit_" + randstr()
48
+ program += f'''
49
+ git add .; git commit -am "{mess}"
50
+ '''
51
+ if action == GitAction.push or action == GitAction.pull:
52
+ # remotes = tm.run(f"cd {path}; git remote", shell="powershell").op.split("\n")
53
+ action_name = "pull" if action == GitAction.pull else "push"
54
+ cmds = [f'echo "pulling from {remote.url}" ; git {action_name} {remote.name} {repo.active_branch.name}' for remote in repo.remotes]
55
+ program += '\n' + '\n'.join(cmds) + '\n'
56
+ program = program + f'''
57
+ echo ""; echo ""
58
+ '''
59
+ return program
60
+
61
+
62
+ def main():
63
+ parser = argparse.ArgumentParser(description='REPO MANAGER')
64
+ # POSITIONAL
65
+ parser.add_argument("directory", help="folder containing repos to record a json out of OR a specs json file to follow.", default="")
66
+ # FLAGS
67
+ parser.add_argument("--push", help=f"push", action="store_true")
68
+ parser.add_argument("--pull", help=f"pull", action="store_true")
69
+ parser.add_argument("--commit", help=f"commit", action="store_true")
70
+ parser.add_argument("--all", help=f"pull, commit and push", action="store_true")
71
+ parser.add_argument("--record", help=f"record respos", action="store_true")
72
+ parser.add_argument("--clone", help=f"clone repos from record", action="store_true")
73
+ parser.add_argument("--checkout", help=f"Check out to versions prvided in this json file", action="store_true")
74
+ parser.add_argument("--checkout_to_branch", help="Checkout to the main branch", action="store_true")
75
+ parser.add_argument("--recursive", "-r", help=f"recursive flag", action="store_true")
76
+ # OPTIONAL
77
+ parser.add_argument("--cloud", "-c", help=f"cloud", default=None)
78
+ args = parser.parse_args()
79
+
80
+ if args.directory == "": repos_root = P.home().joinpath("code") # it is a positional argument, can never be empty.
81
+ else: repos_root = P(args.directory).expanduser().absolute()
82
+ _ = install_n_import("git", "gitpython")
83
+
84
+ program = ""
85
+ if args.record:
86
+ res = record_repos(repos_root=str(repos_root))
87
+ pprint(f"Recorded repositories:\n", res)
88
+ save_path = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
89
+ # Save.pickle(obj=res, path=save_path)
90
+ Save.json(obj=res, path=save_path)
91
+ pprint(f"Result pickled at {P(save_path)}")
92
+ if args.cloud is not None: P(save_path).to_cloud(rel2home=True, cloud=args.cloud)
93
+ program += f"""\necho '>>>>>>>>> Finished Recording'\n"""
94
+ elif args.clone or args.checkout or args.checkout_to_branch:
95
+ # preferred_remote = input("Enter preferred remote to use (default: None): ") or ""
96
+ program += f"""\necho '>>>>>>>>> Cloning Repos'\n"""
97
+ if not repos_root.exists() or repos_root.stem != 'repos.json': # user didn't pass absolute path to pickle file, but rather expected it to be in the default save location
98
+ repos_root = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
99
+ if not repos_root.exists():
100
+ if args.cloud is None:
101
+ cloud: str = Read.ini(DEFAULTS_PATH)['general']['rclone_config_name']
102
+ print(f"⚠️ Using default cloud: {cloud}")
103
+ else:
104
+ cloud = args.cloud
105
+ assert cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
106
+ repos_root.from_cloud(cloud=cloud, rel2home=True)
107
+ assert (repos_root.exists() and repos_root.name == 'repos.json') or args.cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
108
+ program += install_repos(specs_path=str(repos_root), clone=args.clone, checkout_to_recorded_commit=args.checkout, checkout_to_branch=args.checkout_to_branch)
109
+ # elif args.checkout is not None:
110
+
111
+ elif args.all or args.commit or args.pull or args.push:
112
+ for a_path in repos_root.search("*"):
113
+ program += f"""echo "{("Handling " + str(a_path)).center(80, "-")}" """
114
+ if args.pull or args.all: program += git_action(path=a_path, action=GitAction.pull, r=args.recursive)
115
+ if args.commit or args.all: program += git_action(a_path, action=GitAction.commit, r=args.recursive)
116
+ if args.push or args.all: program += git_action(a_path, action=GitAction.push, r=args.recursive)
117
+ else: program = "echo 'no action specified, try to pass --push, --pull, --commit or --all'"
118
+ write_shell_script(program, "Script to update repos")
119
+
120
+
121
+ def record_repos(repos_root: str, r: bool = True) -> list[dict[str, Any]]:
122
+ path_obj = P(repos_root).expanduser().absolute()
123
+ if path_obj.is_file(): return []
124
+ search_res = path_obj.search("*", files=False)
125
+ res: list[dict[str, Any]] = []
126
+ for a_search_res in search_res:
127
+ if a_search_res.joinpath(".git").exists():
128
+ res.append(record_a_repo(a_search_res))
129
+ else:
130
+ if r: res += record_repos(str(a_search_res), r=r)
131
+ return res
132
+
133
+
134
+ def record_a_repo(path: P, search_parent_directories: bool = False, preferred_remote: Optional[str] = None):
135
+ from git.repo import Repo
136
+ repo = Repo(path, search_parent_directories=search_parent_directories) # get list of remotes using git python
137
+ repo_root = P(repo.working_dir).absolute()
138
+ remotes = {remote.name: remote.url for remote in repo.remotes}
139
+ if preferred_remote is not None:
140
+ if preferred_remote in remotes: remotes = {preferred_remote: remotes[preferred_remote]}
141
+ else:
142
+ print(f"⚠️ `{preferred_remote=}` not found in {remotes}.")
143
+ preferred_remote = None
144
+ try: commit = repo.head.commit.hexsha
145
+ except ValueError: # look at https://github.com/gitpython-developers/GitPython/issues/1016
146
+ print(f"⚠️ Failed to get latest commit of {repo}")
147
+ # cmd = "git config --global -add safe.directory"
148
+ commit = None
149
+ try: current_branch = repo.head.reference.name # same as repo.active_branch.name
150
+ except TypeError:
151
+ print(f"⁉️ Failed to get current branch of {repo}. It is probably in a detached state.")
152
+ current_branch = None
153
+ res: dict[str, Any] = {"name": repo_root.name, "parent_dir": repo_root.parent.collapseuser().as_posix(),
154
+ "current_branch": current_branch,
155
+ "remotes": remotes, "version": {"branch": current_branch, "commit": commit}}
156
+ return res
157
+
158
+
159
+ def install_repos(specs_path: str, clone: bool = True, checkout_to_recorded_commit: bool = False, checkout_to_branch: bool = False, editable_install: bool = False, preferred_remote: Optional[str] = None):
160
+ program = ""
161
+ path_obj = P(specs_path).expanduser().absolute()
162
+ repos: list[dict[str, Any]] = Read.json(path_obj)
163
+ for repo in repos:
164
+ parent_dir = P(repo["parent_dir"]).expanduser().absolute().create()
165
+ for idx, (remote_name, remote_url) in enumerate(repo["remotes"].items()):
166
+ if clone:
167
+ if idx == 0: # clone
168
+ if preferred_remote is not None:
169
+ if preferred_remote in repo["remotes"]:
170
+ remote_name = preferred_remote
171
+ remote_url: str = repo["remotes"][preferred_remote]
172
+ else:
173
+ print(f"⚠️ `{preferred_remote=}` not found in {repo['remotes']}.")
174
+ # preferred_remote = None
175
+ program += f"\ncd {parent_dir.collapseuser().as_posix()}; git clone {remote_url} --origin {remote_name}"
176
+ program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git remote set-url {remote_name} {remote_url}"
177
+ # the new url-setting to ensure that account name before `@` was not lost (git clone ignores it): https://thisismygitrepo@github.com/thisismygitrepo/crocodile.git
178
+ program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git remote add {remote_name} {remote_url}"
179
+ if checkout_to_recorded_commit:
180
+ commit = repo['version']['commit']
181
+ if isinstance(commit, str): program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git checkout {commit}"
182
+ else: print(f"Skipping {repo['parent_dir']} because it doesn't have a commit recorded. Found {commit}")
183
+ break # you need to checkout only once, no need to repeat for other remotes.
184
+ if checkout_to_branch:
185
+ program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git checkout {repo['current_branch']}"
186
+ break
187
+ if editable_install and idx == 0:
188
+ program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; pip install -e ."
189
+ program += "\n"
190
+ pprint(program)
191
+ return program
192
+
193
+
194
+ if __name__ == '__main__':
195
+ main()