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
@@ -19,7 +19,7 @@ def main():
19
19
  โ”ƒ ๐Ÿš€ FTP File Transfer
20
20
  โ”ƒ ๐Ÿ“‹ Starting transfer process...
21
21
  โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”""")
22
- parser = argparse.ArgumentParser(description='FTP client')
22
+ parser = argparse.ArgumentParser(description="FTP client")
23
23
 
24
24
  parser.add_argument("source", help="source path (machine:path)")
25
25
  parser.add_argument("target", help="target path (machine:path)")
@@ -38,36 +38,44 @@ def main():
38
38
  source_parts = args.source.split(":")
39
39
  machine = source_parts[0]
40
40
  if len(source_parts) > 1 and source_parts[1] == ES: # the source path is to be inferred from target.
41
- if args.target == ES: raise ValueError(f"""
41
+ if args.target == ES:
42
+ raise ValueError(f"""
42
43
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
43
44
  โ”ƒ โŒ Configuration Error
44
45
  โ”ƒ Cannot use expand symbol `{ES}` in both source and target
45
46
  โ”ƒ This creates a cyclical inference dependency
46
47
  โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”""")
47
- else: target = PathExtended(args.target).expanduser().absolute()
48
+ else:
49
+ target = PathExtended(args.target).expanduser().absolute()
48
50
  source = target.collapseuser().as_posix()
49
51
  else:
50
52
  source = ":".join(args.source.split(":")[1:])
51
- if args.target == ES: target = None
52
- else: target = PathExtended(args.target).expanduser().absolute().as_posix()
53
+ if args.target == ES:
54
+ target = None
55
+ else:
56
+ target = PathExtended(args.target).expanduser().absolute().as_posix()
53
57
 
54
58
  elif ":" in args.target and (args.target[1] != ":" if len(args.target) > 1 else True): # avoid the case of "C:/":
55
59
  source_is_remote = False
56
60
  target_parts = args.target.split(":")
57
61
  machine = target_parts[0]
58
62
  if len(target_parts) > 1 and target_parts[1] == ES:
59
- if args.source == ES: raise ValueError(f"""
63
+ if args.source == ES:
64
+ raise ValueError(f"""
60
65
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
61
66
  โ”ƒ โŒ Configuration Error
62
67
  โ”ƒ Cannot use expand symbol `{ES}` in both source and target
63
68
  โ”ƒ This creates a cyclical inference dependency
64
69
  โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”""")
65
- else: source = args.source
70
+ else:
71
+ source = args.source
66
72
  target = None
67
73
  else:
68
74
  target = ":".join(args.target.split(":")[1:])
69
- if args.source == ES: source = None
70
- else: source = PathExtended(args.source).expanduser().absolute()
75
+ if args.source == ES:
76
+ source = None
77
+ else:
78
+ source = PathExtended(args.source).expanduser().absolute()
71
79
 
72
80
  else:
73
81
  raise ValueError("""
@@ -80,8 +88,9 @@ def main():
80
88
  pprint({"source": str(source), "target": str(target), "machine": machine}, "CLI Resolution")
81
89
 
82
90
  from paramiko.ssh_exception import AuthenticationException # type: ignore
91
+
83
92
  try:
84
- ssh = SSH(rf'{machine}')
93
+ ssh = SSH(rf"{machine}")
85
94
  except AuthenticationException:
86
95
  print("""
87
96
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -92,8 +101,9 @@ def main():
92
101
  โ”‚ This exception only handles password authentication
93
102
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€""")
94
103
  import getpass
104
+
95
105
  pwd = getpass.getpass()
96
- ssh = SSH(rf'{machine}', pwd=pwd)
106
+ ssh = SSH(rf"{machine}", pwd=pwd)
97
107
 
98
108
  if args.cloud:
99
109
  print("""
@@ -118,7 +128,7 @@ def main():
118
128
  โ”‚ ๐Ÿ“ฅ Transfer Mode: Remote โ†’ Local
119
129
  โ”‚ Source: {source}
120
130
  โ”‚ Target: {target}
121
- โ”‚ Options: {'ZIP compression' if args.zipFirst else 'No compression'}, {'Recursive' if args.recursive else 'Non-recursive'}
131
+ โ”‚ Options: {"ZIP compression" if args.zipFirst else "No compression"}, {"Recursive" if args.recursive else "Non-recursive"}
122
132
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€""")
123
133
  received_file = ssh.copy_to_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
124
134
  else:
@@ -129,7 +139,7 @@ def main():
129
139
  โ”‚ ๐Ÿ“ค Transfer Mode: Local โ†’ Remote
130
140
  โ”‚ Source: {source}
131
141
  โ”‚ Target: {target}
132
- โ”‚ Options: {'ZIP compression' if args.zipFirst else 'No compression'}, {'Recursive' if args.recursive else 'Non-recursive'}
142
+ โ”‚ Options: {"ZIP compression" if args.zipFirst else "No compression"}, {"Recursive" if args.recursive else "Non-recursive"}
133
143
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€""")
134
144
  received_file = ssh.copy_from_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
135
145
 
@@ -147,5 +157,5 @@ def main():
147
157
  โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”""")
148
158
 
149
159
 
150
- if __name__ == '__main__':
160
+ if __name__ == "__main__":
151
161
  main()
@@ -3,12 +3,13 @@ from pathlib import Path
3
3
 
4
4
  def get_zellij_cmd(wd1: Path, wd2: Path) -> str:
5
5
  _ = wd1, wd2
6
- lines = [""" zellij action new-tab --name gitdiff""",
7
- """zellij action new-pane --direction down --name local --cwd ./data """,
8
- """zellij action write-chars "cd '{wd1}'; git status" """,
9
- """zellij action move-focus up; zellij action close-pane """,
10
- """zellij action new-pane --direction down --name remote --cwd code """,
11
- """zellij action write-chars "cd '{wd2}' """,
12
- """git status" """
6
+ lines = [
7
+ """ zellij action new-tab --name gitdiff""",
8
+ """zellij action new-pane --direction down --name local --cwd ./data """,
9
+ """zellij action write-chars "cd '{wd1}'; git status" """,
10
+ """zellij action move-focus up; zellij action close-pane """,
11
+ """zellij action new-pane --direction down --name remote --cwd code """,
12
+ """zellij action write-chars "cd '{wd2}' """,
13
+ """git status" """,
13
14
  ]
14
15
  return "; ".join(lines)
@@ -4,10 +4,10 @@ from pydantic import ConfigDict
4
4
  from pydantic.dataclasses import dataclass
5
5
  from typing import Optional
6
6
  import os
7
- from machineconfig.utils.utils import DEFAULTS_PATH
7
+ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
- from rich import box # Import box
10
+ from rich import box # Import box
11
11
 
12
12
 
13
13
  console = Console()
@@ -16,10 +16,10 @@ console = Console()
16
16
  class ArgsDefaults:
17
17
  # source: str=None
18
18
  # target: str=None
19
- encrypt: bool=False
20
- zip_: bool=False
21
- overwrite: bool=False
22
- share: bool=False
19
+ encrypt: bool = False
20
+ zip_: bool = False
21
+ overwrite: bool = False
22
+ share: bool = False
23
23
  rel2home = False
24
24
  root = None
25
25
  os_specific = False
@@ -28,12 +28,12 @@ class ArgsDefaults:
28
28
 
29
29
 
30
30
  @dataclass(config=ConfigDict(extra="forbid", frozen=False))
31
- class Args():
31
+ class Args:
32
32
  cloud: Optional[str] = None
33
33
 
34
- zip: bool=ArgsDefaults.zip_
35
- overwrite: bool=ArgsDefaults.overwrite
36
- share: bool=ArgsDefaults.share
34
+ zip: bool = ArgsDefaults.zip_
35
+ overwrite: bool = ArgsDefaults.overwrite
36
+ share: bool = ArgsDefaults.share
37
37
 
38
38
  root: Optional[str] = ArgsDefaults.root
39
39
  os_specific: bool = ArgsDefaults.os_specific
@@ -67,15 +67,16 @@ def find_cloud_config(path: Path):
67
67
 
68
68
  def absolute(path: str) -> Path:
69
69
  obj = Path(path).expanduser()
70
- if not path.startswith(".") and obj.exists(): return obj
71
- try_absing = Path.cwd().joinpath(path)
72
- if try_absing.exists(): return try_absing
70
+ if not path.startswith(".") and obj.exists():
71
+ return obj
72
+ try_absing = Path.cwd().joinpath(path)
73
+ if try_absing.exists():
74
+ return try_absing
73
75
  display_warning(f"Path {path} could not be resolved to absolute path.")
74
76
  display_warning("Trying to resolve symlinks (this may result in unintended paths).")
75
77
  return obj.absolute()
76
78
 
77
79
 
78
-
79
80
  def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Args:
80
81
  console = Console()
81
82
  console.print(Panel("๐Ÿ” Secure Share Cloud Configuration", expand=False))
@@ -88,10 +89,10 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
88
89
  console.print(f"โ˜๏ธ Using cloud from environment: {cloud}")
89
90
  else:
90
91
  try:
91
- default_cloud__ = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
92
+ default_cloud__ = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
92
93
  except Exception:
93
- default_cloud__ = 'No default cloud found.'
94
- if default_cloud__ == 'No default cloud found.' or interactive:
94
+ default_cloud__ = "No default cloud found."
95
+ if default_cloud__ == "No default cloud found." or interactive:
95
96
  # assert default_cloud is not None
96
97
  cloud = input(f"โ˜๏ธ Enter cloud name (default {default_cloud__}): ") or default_cloud__
97
98
  else:
@@ -106,32 +107,36 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
106
107
  pwd = ""
107
108
  default_message = "no default password found"
108
109
  pwd = input(f"๐Ÿ”‘ Enter encryption password ({default_message}): ") or pwd
109
- res = Args(cloud=cloud,
110
- pwd=pwd, encrypt=True,
111
- zip=True, overwrite=True, share=True,
112
- rel2home=True, root="myshare", os_specific=False,)
110
+ res = Args(cloud=cloud, pwd=pwd, encrypt=True, zip=True, overwrite=True, share=True, rel2home=True, root="myshare", os_specific=False)
113
111
 
114
112
  display_success("Using SecureShare cloud config")
115
113
  pprint(res.__dict__, "SecureShare Config")
116
114
  return res
117
115
 
116
+
118
117
  def display_header(title: str):
119
- console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left")) # Replace print with Panel
118
+ console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left")) # Replace print with Panel
119
+
120
120
 
121
121
  def display_subheader(title: str):
122
- console.print(Panel(title, box=box.ROUNDED, title_align="left")) # Replace print with Panel
122
+ console.print(Panel(title, box=box.ROUNDED, title_align="left")) # Replace print with Panel
123
+
123
124
 
124
125
  def display_content(content: str):
125
- console.print(Panel(content, box=box.ROUNDED, title_align="left")) # Replace print with Panel
126
+ console.print(Panel(content, box=box.ROUNDED, title_align="left")) # Replace print with Panel
127
+
126
128
 
127
129
  def display_status(status: str):
128
- console.print(Panel(status, box=box.ROUNDED, title_align="left")) # Replace print with Panel
130
+ console.print(Panel(status, box=box.ROUNDED, title_align="left")) # Replace print with Panel
131
+
129
132
 
130
133
  def display_success(message: str):
131
- console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left")) # Replace print with Panel
134
+ console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left")) # Replace print with Panel
135
+
132
136
 
133
137
  def display_warning(message: str):
134
- console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left")) # Replace print with Panel
138
+ console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left")) # Replace print with Panel
139
+
135
140
 
136
141
  def display_error(message: str):
137
- console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left")) # Replace print with Panel
142
+ console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left")) # Replace print with Panel
@@ -1,5 +1,5 @@
1
1
  from machineconfig.scripts.python.helpers.cloud_helpers import Args, ArgsDefaults, absolute, find_cloud_config, get_secure_share_cloud_config
2
- from machineconfig.utils.utils import DEFAULTS_PATH
2
+ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
3
3
  from machineconfig.utils.utils2 import read_ini, pprint
4
4
  from typing import Optional
5
5
  from rich.console import Console
@@ -17,7 +17,8 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
17
17
  if config == "ss":
18
18
  cloud_maybe: Optional[str] = target.split(":")[0]
19
19
  # if cloud_maybe == "": cloud_maybe = source.split(":")[0]
20
- if cloud_maybe == "": cloud_maybe = None
20
+ if cloud_maybe == "":
21
+ cloud_maybe = None
21
22
  print("cloud_maybe:", cloud_maybe)
22
23
  maybe_config = get_secure_share_cloud_config(interactive=True, cloud=cloud_maybe)
23
24
  elif config is not None:
@@ -27,13 +28,20 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
27
28
  maybe_config = None
28
29
 
29
30
  if maybe_config is not None:
30
- if args.zip == ArgsDefaults.zip_: args.zip = maybe_config.zip
31
- if args.encrypt == ArgsDefaults.encrypt: args.encrypt = maybe_config.encrypt
32
- if args.share == ArgsDefaults.share: args.share = maybe_config.share
33
- if args.root == ArgsDefaults.root: args.root = maybe_config.root
34
- if args.rel2home == ArgsDefaults.rel2home: args.rel2home = maybe_config.rel2home
35
- if args.pwd == ArgsDefaults.pwd: args.pwd = maybe_config.pwd
36
- if args.os_specific == ArgsDefaults.os_specific: args.os_specific = maybe_config.os_specific
31
+ if args.zip == ArgsDefaults.zip_:
32
+ args.zip = maybe_config.zip
33
+ if args.encrypt == ArgsDefaults.encrypt:
34
+ args.encrypt = maybe_config.encrypt
35
+ if args.share == ArgsDefaults.share:
36
+ args.share = maybe_config.share
37
+ if args.root == ArgsDefaults.root:
38
+ args.root = maybe_config.root
39
+ if args.rel2home == ArgsDefaults.rel2home:
40
+ args.rel2home = maybe_config.rel2home
41
+ if args.pwd == ArgsDefaults.pwd:
42
+ args.pwd = maybe_config.pwd
43
+ if args.os_specific == ArgsDefaults.os_specific:
44
+ args.os_specific = maybe_config.os_specific
37
45
 
38
46
  root = args.root
39
47
  rel2home = args.rel2home
@@ -53,7 +61,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
53
61
  maybe_config = tmp_maybe_config
54
62
 
55
63
  if maybe_config is None:
56
- default_cloud: str=read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
64
+ default_cloud: str = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
57
65
  console.print(Panel(f"โš ๏ธ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
58
66
  source = default_cloud + ":" + source[1:]
59
67
  else:
@@ -73,7 +81,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
73
81
  maybe_config = find_cloud_config(path)
74
82
 
75
83
  if maybe_config is None:
76
- default_cloud = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
84
+ default_cloud = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
77
85
  console.print(Panel(f"โš ๏ธ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
78
86
  target = default_cloud + ":" + target[1:]
79
87
  print("target mutated to:", target, f"because of default cloud being {default_cloud}")
@@ -88,7 +96,6 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
88
96
  zip_arg = tmp.zip
89
97
  share = tmp.share
90
98
 
91
-
92
99
  if ":" in source and (source[1] != ":" if len(source) > 1 else True): # avoid the deceptive case of "C:/"
93
100
  source_parts: list[str] = source.split(":")
94
101
  cloud = source_parts[0]
@@ -97,6 +104,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
97
104
  assert ES not in target, f"You can't use expand symbol `{ES}` in both source and target. Cyclical inference dependency arised."
98
105
  target_obj = absolute(target)
99
106
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
107
+
100
108
  remote_path = PathExtended(target_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
101
109
  source = f"{cloud}:{remote_path.as_posix()}"
102
110
  else: # source path is mentioned, target? maybe.
@@ -116,6 +124,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
116
124
  assert ES not in source, "You can't use $ in both source and target. Cyclical inference dependency arised."
117
125
  source_obj = absolute(source)
118
126
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
127
+
119
128
  remote_path = PathExtended(source_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
120
129
  target = f"{cloud}:{remote_path.as_posix()}"
121
130
  else: # target path is mentioned, source? maybe.
@@ -124,8 +133,10 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
124
133
  raise NotImplementedError("There is no .get_local_path method yet")
125
134
  else:
126
135
  source_obj = absolute(source)
127
- if zip_arg and ".zip" not in target: target += ".zip"
128
- if encrypt and ".enc" not in target: target += ".enc"
136
+ if zip_arg and ".zip" not in target:
137
+ target += ".zip"
138
+ if encrypt and ".enc" not in target:
139
+ target += ".enc"
129
140
  else:
130
141
  console.print(Panel("โŒ ERROR: Invalid path configuration\nEither source or target must be a remote path (i.e. machine:path)", title="[bold red]Error[/bold red]", border_style="red"))
131
142
  raise ValueError(f"Either source or target must be a remote path (i.e. machine:path)\nGot: source: `{source}`, target: `{target}`")
@@ -1,7 +1,7 @@
1
-
2
1
  from typing import Any, Callable, Optional
3
2
  import inspect
4
3
  import os
4
+
5
5
  # import argparse
6
6
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
7
  # from machineconfig.utils.utils import choose_ssh_host
@@ -27,7 +27,7 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
27
27
  # https://google.github.io/python-fire/guide/
28
28
  # https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing
29
29
  if not kwargs: # empty dict
30
- kwargs_str = ''
30
+ kwargs_str = ""
31
31
  else:
32
32
  # For fire module, all keyword arguments should be passed as --key value pairs
33
33
  tmp_list: list[str] = []
@@ -40,40 +40,47 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
40
40
  def parse_pyfile(file_path: str):
41
41
  print(f"๐Ÿ” Loading {file_path} ...")
42
42
  from typing import NamedTuple
43
+
43
44
  args_spec = NamedTuple("args_spec", [("name", str), ("type", str), ("default", Optional[str])])
44
45
  func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
45
-
46
46
  import ast
47
- parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding='utf-8'))
48
- functions = [
49
- node
50
- for node in ast.walk(parsed_ast)
51
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
52
- ]
47
+
48
+ parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding="utf-8"))
49
+ functions = [node for node in ast.walk(parsed_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
53
50
  module__doc__ = ast.get_docstring(parsed_ast)
54
51
  main_option = f"RUN AS MAIN -- {module__doc__ if module__doc__ is not None else 'NoDocs'}"
55
52
  options = [main_option]
56
53
  for function in functions:
57
- if function.name.startswith('__') and function.name.endswith('__'): continue
58
- if any(arg.arg == 'self' for arg in function.args.args): continue
59
- if any(arg.arg == 'self' for arg in function.args.args): continue
54
+ if function.name.startswith("__") and function.name.endswith("__"):
55
+ continue
56
+ if any(arg.arg == "self" for arg in function.args.args):
57
+ continue
58
+ if any(arg.arg == "self" for arg in function.args.args):
59
+ continue
60
60
  doc_string_tmp: str | None = ast.get_docstring(function)
61
- if doc_string_tmp is None: doc_string = "NoDocs"
62
- else: doc_string = doc_string_tmp.replace('\n', ' ')
61
+ if doc_string_tmp is None:
62
+ doc_string = "NoDocs"
63
+ else:
64
+ doc_string = doc_string_tmp.replace("\n", " ")
63
65
  options.append(f"{function.name} -- {', '.join([arg.arg for arg in function.args.args])} -- {doc_string}")
64
66
  tmp = []
65
67
  for idx, arg in enumerate(function.args.args):
66
68
  if arg.annotation is not None:
67
- try: type_ = arg.annotation.__dict__['id']
69
+ try:
70
+ type_ = arg.annotation.__dict__["id"]
68
71
  except KeyError as ke:
69
72
  type_ = "Any" # e.g. a callable object
70
73
  _ = ke
71
- else: type_ = "Any"
74
+ else:
75
+ type_ = "Any"
72
76
  default_tmp = function.args.defaults[idx] if idx < len(function.args.defaults) else None
73
- if default_tmp is None: default = None
77
+ if default_tmp is None:
78
+ default = None
74
79
  else:
75
- if hasattr(default_tmp, "__dict__"): default = default_tmp.__dict__.get("value", None)
76
- else: default = None
80
+ if hasattr(default_tmp, "__dict__"):
81
+ default = default_tmp.__dict__.get("value", None)
82
+ else:
83
+ default = None
77
84
  tmp.append(args_spec(name=arg.arg, type=type_, default=default))
78
85
  func_args.append(tmp)
79
86
  return options, func_args
@@ -85,26 +92,32 @@ def interactively_run_function(func: Callable[[Any], Any]):
85
92
  args = []
86
93
  kwargs = {}
87
94
  for param in params:
88
- if param.annotation is not inspect.Parameter.empty: hint = f" ({param.annotation.__name__})"
89
- else: hint = ""
95
+ if param.annotation is not inspect.Parameter.empty:
96
+ hint = f" ({param.annotation.__name__})"
97
+ else:
98
+ hint = ""
90
99
  if param.default is not inspect.Parameter.empty:
91
100
  default = param.default
92
101
  value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) (default = {default}) : ")
93
- if value == "": value = default
94
- else: value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
102
+ if value == "":
103
+ value = default
104
+ else:
105
+ value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
95
106
  try:
96
107
  if param.annotation is not inspect.Parameter.empty:
97
108
  value = param.annotation
98
109
  except (TypeError, ValueError) as err:
99
110
  raise ValueError(f"Invalid input: {value} is not of type {param.annotation}") from err
100
- if param.kind == inspect.Parameter.KEYWORD_ONLY: kwargs[param.name] = value
101
- else: args.append((param.name, value))
111
+ if param.kind == inspect.Parameter.KEYWORD_ONLY:
112
+ kwargs[param.name] = value
113
+ else:
114
+ args.append((param.name, value))
102
115
  args_to_kwargs = dict(args)
103
116
  return args_to_kwargs, kwargs
104
117
 
105
118
 
106
119
  def get_attrs_recursively(obj: Any):
107
- if hasattr(obj, '__dict__'):
120
+ if hasattr(obj, "__dict__"):
108
121
  res = {}
109
122
  for k, v in obj.__dict__.items():
110
123
  res[k] = get_attrs_recursively(v)
@@ -127,8 +140,8 @@ def get_attrs_recursively(obj: Any):
127
140
 
128
141
 
129
142
  def find_repo_root_path(start_path: str) -> Optional[str]:
130
- root_files = ['setup.py', 'pyproject.toml', '.git']
131
- path: str=start_path
143
+ root_files = ["setup.py", "pyproject.toml", ".git"]
144
+ path: str = start_path
132
145
  trials = 0
133
146
  root_path = os.path.abspath(os.sep)
134
147
  while path != root_path and trials < 20:
@@ -144,12 +157,12 @@ def find_repo_root_path(start_path: str) -> Optional[str]:
144
157
  def get_import_module_code(module_path: str):
145
158
  root_path = find_repo_root_path(module_path)
146
159
  if root_path is None: # just make a desperate attempt to import it
147
- module_name = module_path.lstrip(os.sep).replace(os.sep, '.')
160
+ module_name = module_path.lstrip(os.sep).replace(os.sep, ".")
148
161
  if module_name.endswith(".py"):
149
162
  module_name = module_name[:-3]
150
163
  else:
151
- relative_path = module_path.replace(root_path, '')
152
- module_name = relative_path.lstrip(os.sep).replace(os.sep, '.')
164
+ relative_path = module_path.replace(root_path, "")
165
+ module_name = relative_path.lstrip(os.sep).replace(os.sep, ".")
153
166
  if module_name.endswith(".py"):
154
167
  module_name = module_name[:-3]
155
168
  # module_name = module_name.replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "")
@@ -47,4 +47,4 @@ def get_jupyter_notebook(python_code: str):
47
47
  # nb: NotebookNode = nbf.v4.new_notebook()
48
48
  # nb.cells.append(nbf.v4.new_code_cell(py_script))
49
49
  # with open("new_notebook.ipynb", mode="w", encoding="utf-8") as f:
50
- # nbf.write(nb,f)
50
+ # nbf.write(nb,f)
@@ -1,7 +1,7 @@
1
1
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.terminal import Terminal
3
3
  from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
4
- from machineconfig.utils.utils import CONFIG_PATH, DEFAULTS_PATH
4
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
5
5
  from machineconfig.utils.utils2 import read_ini
6
6
  from machineconfig.utils.code import write_shell_script_to_file
7
7
  import platform
@@ -19,10 +19,12 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
19
19
  print("๐Ÿงน Removed temporary remote copy")
20
20
  from git.remote import Remote
21
21
  from git.repo import Repo
22
+
22
23
  try:
23
24
  Remote.remove(Repo(repo_root_path), "originEnc")
24
25
  console.print(Panel("๐Ÿ”— Removed originEnc remote reference", border_style="blue"))
25
- except Exception: pass # type: ignore
26
+ except Exception:
27
+ pass # type: ignore
26
28
  console.print(Panel("๐Ÿ“ˆ Deleting remote repository copy and pushing local changes", width=150, border_style="blue"))
27
29
 
28
30
  repo_root_path.to_cloud(cloud=cloud, zip=True, encrypt=True, rel2home=True, os_specific=False)
@@ -37,7 +39,7 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
37
39
  def get_wt_cmd(wd1: PathExtended, wd2: PathExtended) -> str:
38
40
  lines = [
39
41
  f"""wt --window 0 new-tab --profile pwsh --title "gitdiff" --tabColor `#3b04d1 --startingDirectory {wd1} ` --colorScheme "Solarized Dark" """,
40
- f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """
42
+ f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """,
41
43
  ]
42
44
  return " `; ".join(lines)
43
45
 
@@ -53,24 +55,24 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
53
55
  program = get_zellij_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_remote_root))
54
56
  write_shell_script_to_file(shell_script=program)
55
57
  return None
56
- else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
58
+ else:
59
+ raise NotImplementedError(f"Platform {platform.system()} not implemented.")
57
60
 
58
61
 
59
62
  def fetch_dotfiles():
60
63
  console.print(Panel("๐Ÿ“ Fetching Dotfiles", title="[bold blue]Dotfiles[/bold blue]", border_style="blue"))
61
64
 
62
- cloud_resolved = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
65
+ cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
63
66
  console.print(Panel(f"โš ๏ธ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", width=150, border_style="yellow"))
64
67
 
65
68
  dotfiles_local = PathExtended.home().joinpath("dotfiles")
66
69
  CONFIG_PATH.joinpath("remote").mkdir(parents=True, exist_ok=True)
67
- dotfiles_remote = CONFIG_PATH.joinpath("remote", dotfiles_local.rel2home())
70
+ dotfiles_remote = PathExtended(CONFIG_PATH).joinpath("remote", dotfiles_local.rel2home())
68
71
  remote_path = dotfiles_local.get_remote_path(rel2home=True, os_specific=False, root="myhome") + ".zip.enc"
69
72
 
70
73
  console.print(Panel("๐Ÿ“ฅ Downloading dotfiles from cloud...", width=150, border_style="blue"))
71
74
 
72
- dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved,
73
- unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
75
+ dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved, unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
74
76
 
75
77
  console.print(Panel("๐Ÿ—‘๏ธ Removing old dotfiles and replacing with cloud version...", width=150, border_style="blue"))
76
78
 
@@ -80,7 +82,8 @@ def fetch_dotfiles():
80
82
  # rm -rf {dotfiles_local}
81
83
  # mv {dotfiles_remote} {dotfiles_local}
82
84
  """
83
- if platform.system() == "Linux": script += """
85
+ if platform.system() == "Linux":
86
+ script += """
84
87
  sudo chmod 600 $HOME/.ssh/*
85
88
  sudo chmod 700 $HOME/.ssh
86
89
  sudo chmod +x $HOME/dotfiles/scripts/linux -R
@@ -90,3 +93,22 @@ sudo chmod +x $HOME/dotfiles/scripts/linux -R
90
93
 
91
94
  console.print(Panel("โœ… Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
92
95
 
96
+
97
+ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
98
+ dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
99
+ from git import Repo
100
+
101
+ repo = Repo(path=dotfiles_path)
102
+ last_commit = repo.head.commit
103
+ dtm = last_commit.committed_datetime
104
+ from datetime import datetime # make it tz unaware
105
+
106
+ dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
107
+ res = dtm > datetime.fromisoformat(commit_dtm)
108
+ if res is False and update is True:
109
+ console = Console()
110
+ console.print(Panel(f"๐Ÿ”„ UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
111
+ from machineconfig.scripts.python.cloud_repo_sync import main
112
+
113
+ main(cloud=None, path=dotfiles_path)
114
+ return res