machineconfig 2.1__py3-none-any.whl → 2.3__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (127) hide show
  1. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
  2. machineconfig/cluster/sessions_managers/layout_types.py +29 -0
  3. machineconfig/cluster/sessions_managers/wt_local.py +68 -62
  4. machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
  5. machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
  6. machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
  7. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
  8. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
  9. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
  10. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
  11. machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
  12. machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
  13. machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
  14. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
  15. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
  16. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
  17. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
  18. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
  19. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
  20. machineconfig/cluster/templates/utils.py +0 -35
  21. machineconfig/jobs/python/check_installations.py +1 -1
  22. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  23. machineconfig/jobs/python_generic_installers/config.json +1 -1
  24. machineconfig/profile/create.py +13 -4
  25. machineconfig/profile/create_hardlinks.py +3 -1
  26. machineconfig/profile/shell.py +8 -7
  27. machineconfig/scripts/__init__.py +0 -2
  28. machineconfig/scripts/linux/devops +6 -4
  29. machineconfig/scripts/python/ai/generate_files.py +14 -15
  30. machineconfig/scripts/python/ai/mcinit.py +8 -5
  31. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  32. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  33. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  34. machineconfig/scripts/python/cloud_copy.py +22 -13
  35. machineconfig/scripts/python/cloud_mount.py +35 -23
  36. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  37. machineconfig/scripts/python/cloud_sync.py +4 -4
  38. machineconfig/scripts/python/croshell.py +37 -28
  39. machineconfig/scripts/python/devops.py +46 -27
  40. machineconfig/scripts/python/devops_add_identity.py +15 -25
  41. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  42. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  43. machineconfig/scripts/python/devops_devapps_install.py +26 -20
  44. machineconfig/scripts/python/devops_update_repos.py +142 -57
  45. machineconfig/scripts/python/dotfile.py +16 -14
  46. machineconfig/scripts/python/fire_agents.py +30 -23
  47. machineconfig/scripts/python/fire_jobs.py +86 -98
  48. machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
  49. machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
  50. machineconfig/scripts/python/ftpx.py +24 -14
  51. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  52. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  53. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  54. machineconfig/scripts/python/helpers/helpers4.py +44 -31
  55. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  56. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  57. machineconfig/scripts/python/mount_nfs.py +8 -15
  58. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  59. machineconfig/scripts/python/mount_ssh.py +8 -6
  60. machineconfig/scripts/python/repos.py +215 -57
  61. machineconfig/scripts/python/snapshot.py +0 -1
  62. machineconfig/scripts/python/start_slidev.py +10 -5
  63. machineconfig/scripts/python/start_terminals.py +22 -16
  64. machineconfig/scripts/python/viewer_template.py +0 -1
  65. machineconfig/scripts/python/wifi_conn.py +49 -76
  66. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  67. machineconfig/settings/lf/linux/lfrc +1 -0
  68. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  70. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  71. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  72. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  73. machineconfig/utils/code.py +2 -3
  74. machineconfig/utils/installer.py +2 -2
  75. machineconfig/utils/installer_utils/installer_abc.py +2 -4
  76. machineconfig/utils/installer_utils/installer_class.py +6 -4
  77. machineconfig/utils/links.py +103 -33
  78. machineconfig/utils/notifications.py +52 -38
  79. machineconfig/utils/options.py +14 -21
  80. machineconfig/utils/path.py +12 -12
  81. machineconfig/utils/path_reduced.py +239 -200
  82. machineconfig/utils/procs.py +1 -1
  83. machineconfig/utils/source_of_truth.py +27 -0
  84. machineconfig/utils/ssh.py +9 -19
  85. machineconfig/utils/terminal.py +4 -2
  86. machineconfig/utils/upgrade_packages.py +91 -0
  87. machineconfig/utils/utils2.py +1 -2
  88. machineconfig/utils/utils5.py +23 -11
  89. machineconfig/utils/ve.py +4 -1
  90. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
  91. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
  92. machineconfig-2.3.dist-info/entry_points.txt +2 -0
  93. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
  94. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
  95. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  96. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  97. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  98. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  99. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  100. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  112. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  113. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  114. machineconfig/utils/utils.py +0 -97
  115. /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
  116. /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
  117. /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
  118. /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
  119. /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
  120. /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
  121. /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
  122. /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
  123. /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
  124. /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
  125. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  126. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
  127. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,13 @@
1
- """NFS mounting script
2
- """
1
+ """NFS mounting script"""
3
2
 
4
3
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
4
  from machineconfig.utils.ssh import SSH
6
5
  from machineconfig.utils.terminal import Terminal
7
- from machineconfig.utils.utils import display_options, PROGRAM_PATH, choose_ssh_host
6
+ from machineconfig.utils.options import display_options, choose_ssh_host
7
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
8
8
  import platform
9
9
 
10
+
10
11
  def main():
11
12
  print("\n" + "=" * 50)
12
13
  print("🚀 Starting NFS Mounting Process")
@@ -19,11 +20,7 @@ def main():
19
20
  assert isinstance(tmp, str)
20
21
  ssh = SSH(tmp)
21
22
  default = f"{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_nfs"
22
- share_info = display_options(
23
- "📂 Choose a share path:",
24
- options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default],
25
- default=default
26
- )
23
+ share_info = display_options("📂 Choose a share path:", options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default], default=default)
27
24
  assert isinstance(share_info, str), f"❌ share_info must be a string. Got {type(share_info)}"
28
25
 
29
26
  remote_server = share_info.split(":")[0]
@@ -41,12 +38,7 @@ def main():
41
38
  mount_path_3 = mount_path_2
42
39
 
43
40
  print("🔧 Preparing mount paths...")
44
- local_mount_point = display_options(
45
- msg="📂 Choose mount path OR input custom one:",
46
- options=[mount_path_1, mount_path_2, mount_path_3],
47
- default=mount_path_2,
48
- custom_input=True
49
- )
41
+ local_mount_point = display_options(msg="📂 Choose mount path OR input custom one:", options=[mount_path_1, mount_path_2, mount_path_3], default=mount_path_2, custom_input=True)
50
42
  assert isinstance(local_mount_point, PathExtended), f"❌ local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
51
43
  local_mount_point = PathExtended(local_mount_point).expanduser()
52
44
 
@@ -79,5 +71,6 @@ $driveLetter = "{driver_letter}"
79
71
 
80
72
  print("🎉 NFS Mounting Process Completed Successfully!\n")
81
73
 
82
- if __name__ == '__main__':
74
+
75
+ if __name__ == "__main__":
83
76
  main()
@@ -1,7 +1,8 @@
1
- from machineconfig.utils.utils import PROGRAM_PATH
1
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
2
2
  from pathlib import Path
3
3
  import platform
4
4
 
5
+
5
6
  def main():
6
7
  print("\n" + "=" * 50)
7
8
  print("🚀 Welcome to the Windows Network Drive Mounting Wizard")
@@ -12,7 +13,7 @@ def main():
12
13
 
13
14
  mount_point_input = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_nw/{machine_name}]: ")
14
15
  if mount_point_input == "":
15
- mount_point = Path.home().joinpath(fr"data/mount_nw/{machine_name}")
16
+ mount_point = Path.home().joinpath(rf"data/mount_nw/{machine_name}")
16
17
  else:
17
18
  mount_point = Path(mount_point_input).expanduser()
18
19
 
@@ -24,13 +25,16 @@ def main():
24
25
 
25
26
  if platform.system() in ["Linux", "Darwin"]:
26
27
  print("\n🔧 Saving configuration for Linux...")
27
- PROGRAM_PATH.write_text(f"""
28
+ PROGRAM_PATH.write_text(
29
+ f"""
28
30
  drive_location='{drive_location}'
29
31
  mount_point='{mount_point}'
30
32
  username='{username}'
31
33
  password='{password}'
32
34
 
33
- """, encoding="utf-8")
35
+ """,
36
+ encoding="utf-8",
37
+ )
34
38
  print("✅ Configuration saved successfully!\n")
35
39
 
36
40
  elif platform.system() == "Windows":
@@ -39,5 +43,6 @@ password='{password}'
39
43
 
40
44
  print("🎉 Windows Network Drive Mounting Process Completed!\n")
41
45
 
42
- if __name__ == '__main__':
46
+
47
+ if __name__ == "__main__":
43
48
  main()
@@ -1,11 +1,12 @@
1
- """Mount a remote SSHFS share on a local directory
2
- """
1
+ """Mount a remote SSHFS share on a local directory"""
3
2
 
4
3
  from platform import system
5
4
  from machineconfig.utils.ssh import SSH
6
5
  from machineconfig.utils.terminal import Terminal
7
6
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
- from machineconfig.utils.utils import PROGRAM_PATH, choose_ssh_host
7
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
8
+ from machineconfig.utils.options import choose_ssh_host
9
+
9
10
 
10
11
  def main():
11
12
  print("\n" + "=" * 50)
@@ -33,7 +34,7 @@ def main():
33
34
 
34
35
  mount_point = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_ssh/{ssh.hostname}]: ")
35
36
  if mount_point == "":
36
- mount_point = PathExtended.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
37
+ mount_point = PathExtended.home().joinpath(rf"data/mount_ssh/{ssh.hostname}")
37
38
 
38
39
  print(f"\n📁 Mount Point: {mount_point}")
39
40
 
@@ -43,7 +44,7 @@ sshfs alex@:/media/dbhdd /media/dbhdd\
43
44
  """
44
45
  print("\n🔧 Preparing SSHFS mount command for Linux...")
45
46
  elif system() == "Windows":
46
- txt = fr"""
47
+ txt = rf"""
47
48
  net use {driver_letter} {share_info}
48
49
  fusermount -u /mnt/dbhdd
49
50
  """
@@ -56,5 +57,6 @@ fusermount -u /mnt/dbhdd
56
57
 
57
58
  print("🎉 SSHFS Mounting Process Completed!\n")
58
59
 
59
- if __name__ == '__main__':
60
+
61
+ if __name__ == "__main__":
60
62
  main()
@@ -5,11 +5,13 @@ in the event that username@github.com is not mentioned in the remote url.
5
5
 
6
6
  """
7
7
 
8
+ import subprocess
8
9
  from rich import print as pprint
9
- from machineconfig.utils.utils import write_shell_script_to_default_program_path, CONFIG_PATH, DEFAULTS_PATH
10
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
10
11
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
11
12
  from machineconfig.utils.io_save import save_json
12
13
  from machineconfig.utils.utils2 import randstr, read_json, read_ini
14
+ from machineconfig.scripts.python.devops_update_repos import run_uv_sync, update_repository
13
15
  import argparse
14
16
  from dataclasses import dataclass
15
17
  from enum import Enum
@@ -30,37 +32,61 @@ class RepoRecord:
30
32
  version: dict[str, str]
31
33
 
32
34
 
33
- def git_action(path: PathExtended, action: GitAction, mess: Optional[str] = None, r: bool = False, uv_sync: bool = False) -> str:
35
+ def git_action(path: PathExtended, action: GitAction, mess: Optional[str] = None, r: bool = False, auto_sync: bool = True) -> bool:
36
+ """Perform git actions using Python instead of shell scripts. Returns True if successful."""
34
37
  from git.exc import InvalidGitRepositoryError
35
38
  from git.repo import Repo
39
+
36
40
  try:
37
41
  repo = Repo(str(path), search_parent_directories=False)
38
42
  except InvalidGitRepositoryError:
39
43
  pprint(f"⚠️ Skipping {path} because it is not a git repository.")
40
44
  if r:
41
- prgs = [git_action(path=sub_path, action=action, mess=mess, r=r, uv_sync=uv_sync) for sub_path in path.search()]
42
- return "\n".join(prgs)
43
- else: return "\necho 'skipped because not a git repo'\n\n"
44
-
45
- program = f'''
46
- echo '>>>>>>>>> 🔧{action}'
47
- cd '{path}'
48
- '''
49
- if action == GitAction.commit:
50
- if mess is None: mess = "auto_commit_" + randstr()
51
- program += f'''
52
- git commit -am "{mess}"
53
- '''
54
- if action == GitAction.push or action == GitAction.pull:
55
- action_name = "pull" if action == GitAction.pull else "push"
56
- cmds = [f'echo "🔄 {action_name.capitalize()}ing from {remote.url}" ; git {action_name} {remote.name} {repo.active_branch.name}' for remote in repo.remotes]
57
- program += '\n' + '\n'.join(cmds) + '\n'
58
- uv_sync_cmd = "uv sync" if uv_sync else ""
59
- program = program + f'''
60
- {uv_sync_cmd}
61
- echo "✅"; echo ""
62
- '''
63
- return program
45
+ results = [git_action(path=sub_path, action=action, mess=mess, r=r, auto_sync=auto_sync) for sub_path in path.search()]
46
+ return all(results) # Return True only if all recursive operations succeeded
47
+ else:
48
+ return False
49
+
50
+ print(f">>>>>>>>> 🔧{action} - {path}")
51
+
52
+ try:
53
+ if action == GitAction.commit:
54
+ if mess is None:
55
+ mess = "auto_commit_" + randstr()
56
+
57
+ # Check if there are changes to commit
58
+ if repo.is_dirty() or repo.untracked_files:
59
+ repo.git.add(A=True) # Stage all changes
60
+ repo.index.commit(mess)
61
+ print(f"✅ Committed changes with message: {mess}")
62
+ return True
63
+ else:
64
+ print("ℹ️ No changes to commit")
65
+ return True
66
+
67
+ elif action == GitAction.push:
68
+ success = True
69
+ for remote in repo.remotes:
70
+ try:
71
+ print(f"🚀 Pushing to {remote.url}")
72
+ remote.push(repo.active_branch.name)
73
+ print(f"✅ Pushed to {remote.name}")
74
+ except Exception as e:
75
+ print(f"❌ Failed to push to {remote.name}: {e}")
76
+ success = False
77
+ return success
78
+
79
+ elif action == GitAction.pull:
80
+ # Use the enhanced update function with uv sync support
81
+ update_repository(repo, auto_sync=auto_sync)
82
+ print("✅ Pull completed")
83
+ return True
84
+
85
+ except Exception as e:
86
+ print(f"❌ Error performing {action} on {path}: {e}")
87
+ return False
88
+
89
+ return True
64
90
 
65
91
 
66
92
  def main():
@@ -68,12 +94,11 @@ def main():
68
94
  print("📂 Welcome to the Repository Manager")
69
95
  print("=" * 50 + "\n")
70
96
 
71
- parser = argparse.ArgumentParser(description='REPO MANAGER')
97
+ parser = argparse.ArgumentParser(description="REPO MANAGER")
72
98
  # POSITIONAL
73
99
  parser.add_argument("directory", help="📁 Folder containing repos to record or a specs JSON file to follow.", default="")
74
100
  # FLAGS
75
101
  parser.add_argument("--push", help="🚀 Push changes.", action="store_true")
76
- parser.add_argument("--uv_sync", help="🔄 Sync UV dependencies.", action="store_true")
77
102
  parser.add_argument("--pull", help="⬇️ Pull changes.", action="store_true")
78
103
  parser.add_argument("--commit", help="💾 Commit changes.", action="store_true")
79
104
  parser.add_argument("--all", help="🔄 Pull, commit, and push changes.", action="store_true")
@@ -82,14 +107,18 @@ def main():
82
107
  parser.add_argument("--checkout", help="🔀 Check out to versions provided in a JSON file.", action="store_true")
83
108
  parser.add_argument("--checkout_to_branch", help="🔀 Check out to the main branch.", action="store_true")
84
109
  parser.add_argument("--recursive", "-r", help="🔍 Recursive flag.", action="store_true")
110
+ parser.add_argument("--no-sync", help="🚫 Disable automatic uv sync after pulls.", action="store_true")
85
111
  # OPTIONAL
86
112
  parser.add_argument("--cloud", "-c", help="☁️ Cloud storage option.", default=None)
87
113
  args = parser.parse_args()
88
114
 
89
- if args.directory == "": repos_root = PathExtended.home().joinpath("code") # it is a positional argument, can never be empty.
90
- else: repos_root = PathExtended(args.directory).expanduser().absolute()
115
+ if args.directory == "":
116
+ repos_root = PathExtended.home().joinpath("code") # it is a positional argument, can never be empty.
117
+ else:
118
+ repos_root = PathExtended(args.directory).expanduser().absolute()
119
+
120
+ auto_sync = not args.no_sync # Enable auto sync by default, disable with --no-sync
91
121
 
92
- program = ""
93
122
  if args.record:
94
123
  print("\n📝 Recording repositories...")
95
124
  res = record_repos(repos_root=str(repos_root))
@@ -97,37 +126,56 @@ def main():
97
126
  save_path = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
98
127
  save_json(obj=res, path=save_path, indent=4)
99
128
  pprint(f"📁 Result saved at {PathExtended(save_path)}")
100
- if args.cloud is not None: PathExtended(save_path).to_cloud(rel2home=True, cloud=args.cloud)
101
- program += """\necho '>>>>>>>>> Finished Recording'\n"""
129
+ if args.cloud is not None:
130
+ PathExtended(save_path).to_cloud(rel2home=True, cloud=args.cloud)
131
+ print(">>>>>>>>> Finished Recording")
132
+
102
133
  elif args.clone or args.checkout or args.checkout_to_branch:
103
134
  print("\n📥 Cloning or checking out repositories...")
104
- program += """\necho '>>>>>>>>> Cloning Repos'\n"""
105
- if not repos_root.exists() or repos_root.name != 'repos.json': # Fixed: use name instead of stem
106
- repos_root = CONFIG_PATH.joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
135
+ print(">>>>>>>>> Cloning Repos")
136
+ if not repos_root.exists() or repos_root.name != "repos.json": # Fixed: use name instead of stem
137
+ repos_root = PathExtended(CONFIG_PATH).joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
107
138
  if not repos_root.exists():
108
139
  if args.cloud is None:
109
- cloud: str=read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
140
+ cloud: str = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
110
141
  print(f"⚠️ Using default cloud: {cloud}")
111
142
  else:
112
143
  cloud = args.cloud
113
144
  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."
114
145
  repos_root.from_cloud(cloud=cloud, rel2home=True)
115
- 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."
116
- program += install_repos(specs_path=str(repos_root), clone=args.clone, checkout_to_recorded_commit=args.checkout, checkout_to_branch=args.checkout_to_branch)
146
+ 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."
147
+ success = install_repos_python(specs_path=str(repos_root), clone=args.clone, checkout_to_recorded_commit=args.checkout, checkout_to_branch=args.checkout_to_branch, auto_sync=auto_sync)
148
+ if success:
149
+ print("✅ Repository operations completed successfully")
150
+ else:
151
+ print("⚠️ Some repository operations encountered issues")
152
+
117
153
  elif args.all or args.commit or args.pull or args.push:
118
154
  print(f"\n🔄 Performing Git actions on repositories @ `{repos_root}`...")
155
+ overall_success = True
119
156
  for a_path in repos_root.search("*"):
120
- program += f"""echo "{("Handling " + str(a_path)).center(80, "-")}" """
121
- if args.pull or args.all: program += git_action(path=a_path, action=GitAction.pull, r=args.recursive, uv_sync=args.uv_sync)
122
- if args.commit or args.all: program += git_action(a_path, action=GitAction.commit, r=args.recursive, uv_sync=args.uv_sync)
123
- if args.push or args.all: program += git_action(a_path, action=GitAction.push, r=args.recursive, uv_sync=args.uv_sync)
124
- else: program = "echo '❌ No action specified. Try passing --push, --pull, --commit, or --all.'"
125
- write_shell_script_to_default_program_path(program=program, desc="Script to update repos", preserve_cwd=True, display=True, execute=False)
157
+ print(f"{('Handling ' + str(a_path)).center(80, '-')}")
158
+ path_success = True
159
+ if args.pull or args.all:
160
+ path_success = git_action(path=a_path, action=GitAction.pull, r=args.recursive, auto_sync=auto_sync) and path_success
161
+ if args.commit or args.all:
162
+ path_success = git_action(a_path, action=GitAction.commit, r=args.recursive, auto_sync=auto_sync) and path_success
163
+ if args.push or args.all:
164
+ path_success = git_action(a_path, action=GitAction.push, r=args.recursive, auto_sync=auto_sync) and path_success
165
+ overall_success = overall_success and path_success
166
+
167
+ if overall_success:
168
+ print("✅ All git operations completed successfully")
169
+ else:
170
+ print("⚠️ Some git operations encountered issues")
171
+ else:
172
+ print("❌ No action specified. Try passing --push, --pull, --commit, or --all.")
126
173
 
127
174
 
128
- def record_repos(repos_root: str, r: bool=True) -> list[dict[str, Any]]:
175
+ def record_repos(repos_root: str, r: bool = True) -> list[dict[str, Any]]:
129
176
  path_obj = PathExtended(repos_root).expanduser().absolute()
130
- if path_obj.is_file(): return []
177
+ if path_obj.is_file():
178
+ return []
131
179
  search_res = path_obj.search("*", files=False)
132
180
  res: list[dict[str, Any]] = []
133
181
  for a_search_res in search_res:
@@ -137,35 +185,145 @@ def record_repos(repos_root: str, r: bool=True) -> list[dict[str, Any]]:
137
185
  except Exception as e:
138
186
  print(f"⚠️ Failed to record {a_search_res}: {e}")
139
187
  else:
140
- if r: res += record_repos(str(a_search_res), r=r)
188
+ if r:
189
+ res += record_repos(str(a_search_res), r=r)
141
190
  return res
142
191
 
143
192
 
144
- def record_a_repo(path: PathExtended, search_parent_directories: bool=False, preferred_remote: Optional[str] = None):
193
+ def record_a_repo(path: PathExtended, search_parent_directories: bool = False, preferred_remote: Optional[str] = None):
145
194
  from git.repo import Repo
195
+
146
196
  repo = Repo(path, search_parent_directories=search_parent_directories) # get list of remotes using git python
147
197
  repo_root = PathExtended(repo.working_dir).absolute()
148
198
  remotes = {remote.name: remote.url for remote in repo.remotes}
149
199
  if preferred_remote is not None:
150
- if preferred_remote in remotes: remotes = {preferred_remote: remotes[preferred_remote]}
200
+ if preferred_remote in remotes:
201
+ remotes = {preferred_remote: remotes[preferred_remote]}
151
202
  else:
152
203
  print(f"⚠️ `{preferred_remote=}` not found in {remotes}.")
153
204
  preferred_remote = None
154
- try: commit = repo.head.commit.hexsha
205
+ try:
206
+ commit = repo.head.commit.hexsha
155
207
  except ValueError: # look at https://github.com/gitpython-developers/GitPython/issues/1016
156
208
  print(f"⚠️ Failed to get latest commit of {repo}")
157
209
  commit = None
158
- try: current_branch = repo.head.reference.name # same as repo.active_branch.name
210
+ try:
211
+ current_branch = repo.head.reference.name # same as repo.active_branch.name
159
212
  except TypeError:
160
213
  print(f"⁉️ Failed to get current branch of {repo}. It is probably in a detached state.")
161
214
  current_branch = None
162
- res: dict[str, Any] = {"name": repo_root.name, "parent_dir": repo_root.parent.collapseuser().as_posix(),
163
- "current_branch": current_branch,
164
- "remotes": remotes, "version": {"branch": current_branch, "commit": commit}}
215
+ res: dict[str, Any] = {"name": repo_root.name, "parent_dir": repo_root.parent.collapseuser().as_posix(), "current_branch": current_branch, "remotes": remotes, "version": {"branch": current_branch, "commit": commit}}
165
216
  return res
166
217
 
167
218
 
168
- 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):
219
+ def install_repos_python(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, auto_sync: bool = True) -> bool:
220
+ """Python-based repository installation with uv sync support. Returns True if all operations succeeded."""
221
+ from git.repo import Repo
222
+ from git.exc import GitCommandError
223
+
224
+ path_obj = PathExtended(specs_path).expanduser().absolute()
225
+ repos: list[dict[str, Any]] = read_json(path_obj)
226
+ overall_success = True
227
+
228
+ for repo in repos:
229
+ repo_success = True
230
+ parent_dir = PathExtended(repo["parent_dir"]).expanduser().absolute()
231
+ parent_dir.mkdir(parents=True, exist_ok=True)
232
+ repo_path = parent_dir / repo["name"]
233
+
234
+ print(f"\n{'Processing ' + repo['name']:.^80}")
235
+
236
+ # Handle cloning and remote setup
237
+ if clone:
238
+ # Select the remote to use for cloning
239
+ if len(repo["remotes"]) == 0:
240
+ print(f"⚠️ No remotes found for {repo['name']}. Skipping clone.")
241
+ repo_success = False
242
+ continue
243
+
244
+ remote_name, remote_url = next(iter(repo["remotes"].items())) # Get first remote by default
245
+ if preferred_remote is not None and preferred_remote in repo["remotes"]:
246
+ remote_name = preferred_remote
247
+ remote_url = repo["remotes"][preferred_remote]
248
+ elif preferred_remote is not None:
249
+ print(f"⚠️ `{preferred_remote=}` not found in {repo['remotes']}.")
250
+
251
+ try:
252
+ # Clone with the selected remote
253
+ print(f"📥 Cloning {remote_url} to {repo_path}")
254
+ cloned_repo = Repo.clone_from(remote_url, repo_path, origin=remote_name, depth=2)
255
+ print(f"✅ Successfully cloned {repo['name']}")
256
+
257
+ # Add any additional remotes
258
+ for other_remote_name, other_remote_url in repo["remotes"].items():
259
+ if other_remote_name != remote_name:
260
+ try:
261
+ cloned_repo.create_remote(other_remote_name, other_remote_url)
262
+ print(f"✅ Added remote {other_remote_name}")
263
+ except Exception as e:
264
+ print(f"⚠️ Failed to add remote {other_remote_name}: {e}")
265
+
266
+ except GitCommandError as e:
267
+ print(f"❌ Failed to clone {repo['name']}: {e}")
268
+ repo_success = False
269
+ continue
270
+ except Exception as e:
271
+ print(f"❌ Unexpected error cloning {repo['name']}: {e}")
272
+ repo_success = False
273
+ continue
274
+
275
+ # Handle checkout operations (after cloning/if repo exists)
276
+ if repo_path.exists():
277
+ try:
278
+ existing_repo = Repo(repo_path)
279
+
280
+ if checkout_to_recorded_commit:
281
+ commit = repo["version"]["commit"]
282
+ if isinstance(commit, str):
283
+ print(f"🔀 Checking out to commit {commit[:8]}...")
284
+ existing_repo.git.checkout(commit)
285
+ print("✅ Checked out to recorded commit")
286
+ else:
287
+ print(f"⚠️ Skipping {repo['name']} because it doesn't have a commit recorded. Found {commit}")
288
+
289
+ elif checkout_to_branch:
290
+ if repo.get("current_branch"):
291
+ print(f"🔀 Checking out to branch {repo['current_branch']}...")
292
+ existing_repo.git.checkout(repo["current_branch"])
293
+ print("✅ Checked out to recorded branch")
294
+ else:
295
+ print(f"⚠️ No current branch recorded for {repo['name']}")
296
+
297
+ # Handle editable install
298
+ if editable_install:
299
+ pyproject_path = repo_path / "pyproject.toml"
300
+ if pyproject_path.exists():
301
+ print(f"📦 Installing {repo['name']} in editable mode...")
302
+ result = subprocess.run(["uv", "pip", "install", "-e", "."], cwd=repo_path, capture_output=True, text=True)
303
+ if result.returncode == 0:
304
+ print("✅ Editable install completed")
305
+ else:
306
+ print(f"❌ Editable install failed: {result.stderr}")
307
+ repo_success = False
308
+ else:
309
+ print(f"⚠️ No pyproject.toml found in {repo['name']}, skipping editable install")
310
+
311
+ # Run uv sync if auto_sync is enabled and pyproject.toml exists
312
+ if auto_sync and (repo_path / "pyproject.toml").exists():
313
+ sync_success = run_uv_sync(repo_path)
314
+ if not sync_success:
315
+ repo_success = False
316
+
317
+ except Exception as e:
318
+ print(f"❌ Error processing existing repository {repo['name']}: {e}")
319
+ repo_success = False
320
+
321
+ overall_success = overall_success and repo_success
322
+
323
+ return overall_success
324
+
325
+
326
+ 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):
169
327
  program = ""
170
328
  path_obj = PathExtended(specs_path).expanduser().absolute()
171
329
  repos: list[dict[str, Any]] = read_json(path_obj)
@@ -197,7 +355,7 @@ def install_repos(specs_path: str, clone: bool=True, checkout_to_recorded_commit
197
355
 
198
356
  # Handle checkout operations (after all remotes are set up)
199
357
  if checkout_to_recorded_commit:
200
- commit = repo['version']['commit']
358
+ commit = repo["version"]["commit"]
201
359
  if isinstance(commit, str):
202
360
  program += f"\ncd {parent_dir.collapseuser().as_posix()}/{repo['name']}; git checkout {commit}"
203
361
  else:
@@ -214,5 +372,5 @@ def install_repos(specs_path: str, clone: bool=True, checkout_to_recorded_commit
214
372
  return program
215
373
 
216
374
 
217
- if __name__ == '__main__':
375
+ if __name__ == "__main__":
218
376
  main()
@@ -1,4 +1,3 @@
1
-
2
1
  # def main():
3
2
  # print("\n" + "=" * 50)
4
3
  # print("📸 Welcome to the Snapshot Tool")
@@ -2,7 +2,8 @@
2
2
  slidev
3
3
  """
4
4
 
5
- from machineconfig.utils.utils import CONFIG_PATH, PROGRAM_PATH, print_code
5
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, PROGRAM_PATH
6
+ from machineconfig.utils.code import print_code
6
7
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
8
  from machineconfig.utils.terminal import Terminal
8
9
  import subprocess
@@ -10,12 +11,13 @@ import platform
10
11
 
11
12
  PORT_DEFAULT = 3030
12
13
 
13
- SLIDEV_REPO = CONFIG_PATH.joinpath(".cache/slidev")
14
+ SLIDEV_REPO = PathExtended(CONFIG_PATH).joinpath(".cache/slidev")
14
15
  if not SLIDEV_REPO.joinpath("components").exists():
15
16
  print("📦 Initializing Slidev repository...")
16
17
  Terminal(stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE).run(f"cd {SLIDEV_REPO.parent};npm init slidev@latest")
17
18
  print("✅ Slidev repository initialized successfully!\n")
18
19
 
20
+
19
21
  def jupyter_to_markdown(file: PathExtended):
20
22
  op_dir = file.parent.joinpath("presentation")
21
23
  print("📝 Converting Jupyter notebook to markdown...")
@@ -36,14 +38,15 @@ def jupyter_to_markdown(file: PathExtended):
36
38
  Terminal().run(cmd, shell="powershell").print()
37
39
 
38
40
  op_file = op_dir.joinpath("slides_raw.md")
39
- slide_separator = '\n\n---\n\n'
40
- md = op_file.read_text(encoding="utf-8").replace('\n\n\n\n', slide_separator)
41
+ slide_separator = "\n\n---\n\n"
42
+ md = op_file.read_text(encoding="utf-8").replace("\n\n\n\n", slide_separator)
41
43
  md = slide_separator.join([item for item in md.split(slide_separator) if bool(item.strip())])
42
44
  op_file.with_name("slides.md").write_text(md, encoding="utf-8")
43
45
  print(f"✅ Conversion completed! Check the results at: {op_dir}\n")
44
46
 
45
47
  return op_dir
46
48
 
49
+
47
50
  def main() -> None:
48
51
  import argparse
49
52
 
@@ -85,6 +88,7 @@ def main() -> None:
85
88
  SLIDEV_REPO.joinpath(md_file.name).with_name(name="slides.md", inplace=True, overwrite=True)
86
89
 
87
90
  import socket
91
+
88
92
  try:
89
93
  local_ip_v4 = socket.gethostbyname(socket.gethostname() + ".local")
90
94
  except Exception:
@@ -100,5 +104,6 @@ def main() -> None:
100
104
  PROGRAM_PATH.write_text(program, encoding="utf-8")
101
105
  print_code(code=program, lexer="bash", desc="Run the following command to start the presentation")
102
106
 
103
- if __name__ == '__main__':
107
+
108
+ if __name__ == "__main__":
104
109
  main()