machineconfig 2.2__py3-none-any.whl → 2.4__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 (101) hide show
  1. machineconfig/__init__.py +30 -0
  2. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
  3. machineconfig/cluster/sessions_managers/layout_types.py +29 -0
  4. machineconfig/cluster/sessions_managers/wt_local.py +68 -62
  5. machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
  6. machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
  7. machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
  8. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
  9. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
  10. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
  11. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
  12. machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
  13. machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
  14. machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
  15. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
  16. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
  17. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
  18. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
  19. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
  20. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
  21. machineconfig/cluster/templates/utils.py +1 -1
  22. machineconfig/profile/create.py +4 -0
  23. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  24. machineconfig/scripts/linux/checkout_versions +1 -7
  25. machineconfig/scripts/linux/choose_wezterm_theme +1 -7
  26. machineconfig/scripts/linux/cloud_copy +1 -8
  27. machineconfig/scripts/linux/cloud_manager +1 -7
  28. machineconfig/scripts/linux/cloud_mount +1 -23
  29. machineconfig/scripts/linux/cloud_repo_sync +1 -21
  30. machineconfig/scripts/linux/cloud_sync +1 -23
  31. machineconfig/scripts/linux/croshell +1 -23
  32. machineconfig/scripts/linux/devops +0 -21
  33. machineconfig/scripts/linux/fire +1 -27
  34. machineconfig/scripts/linux/fire_agents +1 -26
  35. machineconfig/scripts/linux/gh_models +1 -10
  36. machineconfig/scripts/linux/kill_process +1 -9
  37. machineconfig/scripts/linux/mcinit +1 -26
  38. machineconfig/scripts/linux/mount_nfs +1 -13
  39. machineconfig/scripts/linux/repos +1 -23
  40. machineconfig/scripts/linux/scheduler +1 -7
  41. machineconfig/scripts/linux/start_slidev +1 -22
  42. machineconfig/scripts/linux/start_terminals +1 -9
  43. machineconfig/scripts/linux/url2md +1 -9
  44. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  45. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  46. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  47. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  48. machineconfig/scripts/python/cloud_mount.py +4 -2
  49. machineconfig/scripts/python/cloud_repo_sync.py +5 -2
  50. machineconfig/scripts/python/cloud_sync.py +4 -2
  51. machineconfig/scripts/python/croshell.py +5 -3
  52. machineconfig/scripts/python/devops.py +3 -2
  53. machineconfig/scripts/python/devops_devapps_install.py +1 -0
  54. machineconfig/scripts/python/fire_agents.py +6 -6
  55. machineconfig/scripts/python/fire_jobs.py +26 -72
  56. machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
  57. machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
  58. machineconfig/scripts/python/helpers/helpers4.py +0 -1
  59. machineconfig/scripts/python/mount_nfs.py +12 -8
  60. machineconfig/scripts/python/mount_nw_drive.py +6 -6
  61. machineconfig/scripts/python/mount_ssh.py +4 -2
  62. machineconfig/scripts/python/start_slidev.py +4 -2
  63. machineconfig/scripts/python/start_terminals.py +4 -2
  64. machineconfig/scripts/python/wifi_conn.py +0 -1
  65. machineconfig/settings/__pycache__/__init__.cpython-313.pyc +0 -0
  66. machineconfig/settings/shells/ipy/profiles/default/__pycache__/__init__.cpython-313.pyc +0 -0
  67. machineconfig/settings/shells/ipy/profiles/default/startup/__pycache__/__init__.cpython-313.pyc +0 -0
  68. machineconfig/utils/code.py +5 -6
  69. machineconfig/utils/installer_utils/installer_abc.py +0 -1
  70. machineconfig/utils/options.py +7 -7
  71. machineconfig/utils/path.py +12 -12
  72. machineconfig/utils/path_reduced.py +6 -1
  73. machineconfig/utils/source_of_truth.py +2 -2
  74. machineconfig/utils/ssh.py +11 -1
  75. machineconfig/utils/upgrade_packages.py +12 -12
  76. {machineconfig-2.2.dist-info → machineconfig-2.4.dist-info}/METADATA +1 -1
  77. {machineconfig-2.2.dist-info → machineconfig-2.4.dist-info}/RECORD +90 -93
  78. machineconfig-2.4.dist-info/entry_points.txt +2 -0
  79. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -60
  80. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
  81. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  82. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  84. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  85. machineconfig/scripts/linux/archive/tmate_conn +0 -12
  86. machineconfig/scripts/linux/archive/tmate_start +0 -12
  87. machineconfig/scripts/linux/archive/transfer_wsl_win +0 -5
  88. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  89. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  90. /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
  91. /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
  92. /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
  93. /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
  94. /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
  95. /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
  96. /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
  97. /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
  98. /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
  99. /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
  100. {machineconfig-2.2.dist-info → machineconfig-2.4.dist-info}/WHEEL +0 -0
  101. {machineconfig-2.2.dist-info → machineconfig-2.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ from machineconfig.cluster.sessions_managers.layout_types import LayoutConfig, LayoutsFile
3
+ from typing import Optional, TYPE_CHECKING
4
+ from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
5
+ from machineconfig.utils.options import choose_one_option
6
+ from machineconfig.utils.path import match_file_name, sanitize_path
7
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
+
9
+ if TYPE_CHECKING:
10
+ from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
11
+
12
+
13
+ def select_layout(layouts_json_file: Path, layout_name: Optional[str]):
14
+ import json
15
+
16
+ layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
17
+ if len(layout_file["layouts"]) == 0:
18
+ raise ValueError(f"No layouts found in {layouts_json_file}")
19
+ if layout_name is None:
20
+ options = [layout["layoutName"] for layout in layout_file["layouts"]]
21
+ from machineconfig.utils.options import choose_one_option
22
+
23
+ layout_name = choose_one_option(options=options, prompt="Choose a layout configuration:", fzf=True)
24
+ print(f"Selected layout: {layout_name}")
25
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layout_name), None)
26
+ if layout_chosen is None:
27
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layout_name.lower()), None)
28
+ if layout_chosen is None:
29
+ available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
30
+ raise ValueError(f"Layout '{layout_name}' not found. Available layouts: {available_layouts}")
31
+ return layout_chosen
32
+
33
+
34
+ def launch_layout(layout_config: LayoutConfig) -> Optional[Exception]:
35
+ import platform
36
+
37
+ if platform.system() == "Linux" or platform.system() == "Darwin":
38
+ print("🧑‍💻 Launching layout using Zellij terminal multiplexer...")
39
+ from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
40
+
41
+ run_zellij_layout(layout_config=layout_config)
42
+ elif platform.system() == "Windows":
43
+ print("🧑‍💻 Launching layout using Windows Terminal...")
44
+ from machineconfig.cluster.sessions_managers.wt_local import run_wt_layout
45
+
46
+ run_wt_layout(layout_config=layout_config)
47
+ else:
48
+ print(f"❌ Unsupported platform: {platform.system()}")
49
+ return None
50
+
51
+
52
+ def handle_layout_args(args: "FireJobArgs") -> None:
53
+ # args.function = args.path
54
+ # args.path = "layout.json"
55
+ path_obj = sanitize_path(args.path)
56
+ if not path_obj.exists():
57
+ choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes={".json"})
58
+ elif path_obj.is_dir():
59
+ print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
60
+ files = search_for_files_of_interest(path_obj)
61
+ print(f"🔍 Got #{len(files)} results.")
62
+ choice_file = choose_one_option(options=files, fzf=True)
63
+ choice_file = PathExtended(choice_file)
64
+ else:
65
+ choice_file = path_obj
66
+ launch_layout(layout_config=select_layout(layouts_json_file=choice_file, layout_name=args.function))
@@ -43,7 +43,6 @@ def parse_pyfile(file_path: str):
43
43
 
44
44
  args_spec = NamedTuple("args_spec", [("name", str), ("type", str), ("default", Optional[str])])
45
45
  func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
46
-
47
46
  import ast
48
47
 
49
48
  parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding="utf-8"))
@@ -4,7 +4,7 @@ from machineconfig.utils.path_reduced import PathExtended as PathExtended
4
4
  from machineconfig.utils.ssh import SSH
5
5
  from machineconfig.utils.terminal import Terminal
6
6
  from machineconfig.utils.options import display_options, choose_ssh_host
7
- from machineconfig.utils.source_of_truth import PROGRAM_PATH
7
+
8
8
  import platform
9
9
 
10
10
 
@@ -42,12 +42,15 @@ def main():
42
42
  assert isinstance(local_mount_point, PathExtended), f"❌ local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
43
43
  local_mount_point = PathExtended(local_mount_point).expanduser()
44
44
 
45
- PROGRAM_PATH.write_text(f"""
45
+ txt = f"""
46
46
  share_info={share_info}
47
47
  remote_server={remote_server}
48
48
  share_path={share_path}
49
49
  local_mount_point={local_mount_point}
50
- """)
50
+ """
51
+ # PROGRAM_PATH.write_text(txt)
52
+ import subprocess
53
+ subprocess.run(txt, shell=True, check=True)
51
54
 
52
55
  print("✅ Mount paths prepared successfully!\n")
53
56
 
@@ -55,18 +58,19 @@ local_mount_point={local_mount_point}
55
58
  print("\n🔍 Checking existing drives...")
56
59
  print(Terminal().run("Get-PSDrive -PSProvider 'FileSystem'", shell="powershell").op)
57
60
  driver_letter = input(r"🖥️ Choose driver letter (e.g., Z:\\) [Avoid already used ones]: ") or "Z:\\"
58
-
59
- PROGRAM_PATH.write_text(f"""
61
+ txt = f"""
60
62
  $server = "{remote_server}"
61
63
  $sharePath = "{share_path}"
62
64
  $driveLetter = "{driver_letter}"
63
- """)
64
-
65
+ """
66
+ # PROGRAM_PATH.write_text(txt)
67
+ import subprocess
68
+ subprocess.run(txt, shell=True, check=True)
65
69
  print("✅ Drive letter selected and configuration saved!\n")
66
70
 
67
71
  print("\n📄 Configuration File Content:")
68
72
  print("-" * 50)
69
- print(PROGRAM_PATH.read_text(encoding="utf-8"))
73
+ # print(PROGRAM_PATH.read_text(encoding="utf-8"))
70
74
  print("-" * 50 + "\n")
71
75
 
72
76
  print("🎉 NFS Mounting Process Completed Successfully!\n")
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.source_of_truth import PROGRAM_PATH
1
+
2
2
  from pathlib import Path
3
3
  import platform
4
4
 
@@ -25,16 +25,16 @@ def main():
25
25
 
26
26
  if platform.system() in ["Linux", "Darwin"]:
27
27
  print("\n🔧 Saving configuration for Linux...")
28
- PROGRAM_PATH.write_text(
29
- f"""
28
+ txt = f"""
30
29
  drive_location='{drive_location}'
31
30
  mount_point='{mount_point}'
32
31
  username='{username}'
33
32
  password='{password}'
34
33
 
35
- """,
36
- encoding="utf-8",
37
- )
34
+ """
35
+ # PROGRAM_PATH.write_text(txt, encoding="utf-8",)
36
+ import subprocess
37
+ subprocess.run(txt, shell=True, check=True)
38
38
  print("✅ Configuration saved successfully!\n")
39
39
 
40
40
  elif platform.system() == "Windows":
@@ -4,7 +4,7 @@ from platform import system
4
4
  from machineconfig.utils.ssh import SSH
5
5
  from machineconfig.utils.terminal import Terminal
6
6
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
- from machineconfig.utils.source_of_truth import PROGRAM_PATH
7
+
8
8
  from machineconfig.utils.options import choose_ssh_host
9
9
 
10
10
 
@@ -52,7 +52,9 @@ fusermount -u /mnt/dbhdd
52
52
  else:
53
53
  raise ValueError(f"❌ Not implemented for this system: {system()}")
54
54
 
55
- PROGRAM_PATH.write_text(txt, encoding="utf-8")
55
+ # PROGRAM_PATH.write_text(txt, encoding="utf-8")
56
+ import subprocess
57
+ subprocess.run(txt, shell=True, check=True)
56
58
  print("✅ Configuration saved successfully!\n")
57
59
 
58
60
  print("🎉 SSHFS Mounting Process Completed!\n")
@@ -2,7 +2,7 @@
2
2
  slidev
3
3
  """
4
4
 
5
- from machineconfig.utils.source_of_truth import CONFIG_PATH, PROGRAM_PATH
5
+ from machineconfig.utils.source_of_truth import CONFIG_PATH
6
6
  from machineconfig.utils.code import print_code
7
7
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
8
  from machineconfig.utils.terminal import Terminal
@@ -101,7 +101,9 @@ def main() -> None:
101
101
  print(f" - http://{local_ip_v4}:{port}\n")
102
102
 
103
103
  program = "npm run dev slides.md -- --remote"
104
- PROGRAM_PATH.write_text(program, encoding="utf-8")
104
+ # PROGRAM_PATH.write_text(program, encoding="utf-8")
105
+ import subprocess
106
+ subprocess.run(program, shell=True, cwd=SLIDEV_REPO)
105
107
  print_code(code=program, lexer="bash", desc="Run the following command to start the presentation")
106
108
 
107
109
 
@@ -1,6 +1,6 @@
1
1
  """Script to start terminals on windows and wsl"""
2
2
 
3
- from machineconfig.utils.source_of_truth import PROGRAM_PATH
3
+
4
4
  from machineconfig.utils.options import display_options, get_ssh_hosts
5
5
  import platform
6
6
  from itertools import cycle
@@ -111,7 +111,9 @@ def main():
111
111
  print(cmd)
112
112
  print("-" * 50 + "\n")
113
113
 
114
- PROGRAM_PATH.write_text(cmd, encoding="utf-8")
114
+ # PROGRAM_PATH.write_text(cmd, encoding="utf-8")
115
+ import subprocess
116
+ subprocess.run(cmd, shell=True)
115
117
  print("✅ Command saved successfully!\n")
116
118
 
117
119
 
@@ -187,7 +187,6 @@ def connect_to_new_network(ssid: str, password: str):
187
187
  def display_available_networks():
188
188
  """Display available networks (legacy function for compatibility)"""
189
189
  console.print("\n[blue]📡 Scanning for available networks...[/blue]")
190
-
191
190
  try:
192
191
  if platform.system() == "Windows":
193
192
  subprocess.run(["netsh", "wlan", "show", "networks", "interface=Wi-Fi"], check=True)
@@ -1,4 +1,3 @@
1
-
2
1
  import platform
3
2
  from typing import Optional
4
3
  import subprocess
@@ -9,7 +8,7 @@ from rich.syntax import Syntax
9
8
  from machineconfig.utils.utils2 import randstr
10
9
  from machineconfig.utils.ve import get_ve_activate_line
11
10
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
12
- from machineconfig.utils.source_of_truth import PROGRAM_PATH
11
+
13
12
 
14
13
 
15
14
  def get_shell_script_executing_python_file(python_file: str, func: Optional[str], ve_path: str, strict_execution: bool = True):
@@ -85,11 +84,11 @@ def write_shell_script_to_default_program_path(program: str, desc: str, preserve
85
84
  else:
86
85
  program = 'orig_path=$(cd -- "." && pwd)\n' + program + '\ncd "$orig_path" || exit'
87
86
  if display:
88
- print_code(code=program, lexer="shell", desc=desc, subtitle=str(PROGRAM_PATH))
89
- PROGRAM_PATH.parent.mkdir(parents=True, exist_ok=True)
90
- PROGRAM_PATH.write_text(program, encoding="utf-8")
87
+ print_code(code=program, lexer="shell", desc=desc, subtitle="PROGRAM")
88
+ # PROGRAM_PATH.parent.mkdir(parents=True, exist_ok=True)
89
+ # PROGRAM_PATH.write_text(program, encoding="utf-8")
91
90
  if execute:
92
- result = subprocess.run(f". {PROGRAM_PATH}", shell=True, capture_output=True, text=True)
91
+ result = subprocess.run(program, shell=True, capture_output=True, text=True)
93
92
  success = result.returncode == 0 and result.stderr == ""
94
93
  if not success:
95
94
  print("❌ 🛠️ EXECUTION | Shell script running failed")
@@ -1,4 +1,3 @@
1
-
2
1
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
3
2
  from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
4
3
  from typing import Optional, TypeAlias, Literal
@@ -5,25 +5,25 @@ from rich.console import Console
5
5
  import platform
6
6
  import subprocess
7
7
  from typing import Optional, Union, TypeVar, Iterable
8
+ from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
9
+
8
10
 
9
11
  T = TypeVar("T")
10
12
 
11
13
 
12
14
  def check_tool_exists(tool_name: str) -> bool:
13
- if platform.system() == "Windows": tool_name = tool_name.replace(".exe", "") + ".exe"
14
-
15
- from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
16
-
17
15
  if platform.system() == "Windows":
16
+ tool_name = tool_name.replace(".exe", "") + ".exe"
18
17
  cmd = "where.exe"
19
18
  root_path = Path(WINDOWS_INSTALL_PATH)
20
19
  elif platform.system() in ["Linux", "Darwin"]:
21
20
  cmd = "which"
22
21
  root_path = Path(LINUX_INSTALL_PATH)
23
- else: raise NotImplementedError(f"platform {platform.system()} not implemented")
24
-
22
+ return any([Path("/usr/local/bin").joinpath(tool_name).is_file(), Path("/usr/bin").joinpath(tool_name).is_file(), root_path.joinpath(tool_name).is_file()])
23
+ else:
24
+ raise NotImplementedError(f"platform {platform.system()} not implemented")
25
25
  _ = cmd
26
- # try:
26
+ # try: # talking to terminal is too slow.
27
27
  # _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
28
28
  # res: bool = True
29
29
  # except (subprocess.CalledProcessError, FileNotFoundError):
@@ -12,7 +12,7 @@ T = TypeVar("T")
12
12
  console = Console()
13
13
 
14
14
 
15
- def sanitize_path(a_path: PathExtended) -> PathExtended:
15
+ def sanitize_path(a_path: str) -> PathExtended:
16
16
  path = PathExtended(a_path)
17
17
  if Path.cwd() == Path.home() and not path.exists():
18
18
  result = input("Current working directory is home, and passed path is not full path, are you sure you want to continue, [y]/n? ") or "y"
@@ -26,14 +26,14 @@ def sanitize_path(a_path: PathExtended) -> PathExtended:
26
26
  skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
27
27
  path = PathExtended.home().joinpath(*path.parts[skip_parts:])
28
28
  assert path.exists(), f"File not found: {path}"
29
- source_os = "Linux" if a_path.as_posix().startswith("/home") else "macOS"
29
+ source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
30
30
  console.print(Panel(f"🔗 PATH MAPPING | {source_os} → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
31
31
  elif platform.system() in ["Linux", "Darwin"] and PathExtended.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
32
32
  skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
33
33
  path = PathExtended.home().joinpath(*path.parts[skip_parts:])
34
34
  assert path.exists(), f"File not found: {path}"
35
35
  current_os = "Linux" if platform.system() == "Linux" else "macOS"
36
- source_os = "Linux" if a_path.as_posix().startswith("/home") else "macOS"
36
+ source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
37
37
  console.print(Panel(f"🔗 PATH MAPPING | {source_os} → {current_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
38
38
  elif path.as_posix().startswith("C:"):
39
39
  if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
@@ -49,18 +49,18 @@ def sanitize_path(a_path: PathExtended) -> PathExtended:
49
49
  return path.expanduser().absolute()
50
50
 
51
51
 
52
- def find_scripts(root: Path, name_substring: str) -> tuple[list[Path], list[Path]]:
52
+ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[list[Path], list[Path]]:
53
53
  filename_matches = []
54
54
  partial_path_matches = []
55
55
  for entry in root.iterdir():
56
56
  if entry.is_dir():
57
- if entry.name in {".links", ".venv", ".git", ".idea", ".vscode", "node_modules", "__pycache__"}:
57
+ if entry.name in {".links", ".venv", ".git", ".idea", ".vscode", "node_modules", "__pycache__", ".mypy_cache"}:
58
58
  # prune this entire subtree
59
59
  continue
60
- tmp1, tmp2 = find_scripts(entry, name_substring)
60
+ tmp1, tmp2 = find_scripts(entry, name_substring, suffixes)
61
61
  filename_matches.extend(tmp1)
62
62
  partial_path_matches.extend(tmp2)
63
- elif entry.is_file() and entry.suffix in {".py", ".sh", ".ps1"}:
63
+ elif entry.is_file() and entry.suffix in suffixes:
64
64
  if name_substring.lower() in entry.name.lower():
65
65
  filename_matches.append(entry)
66
66
  elif name_substring.lower() in entry.as_posix().lower():
@@ -68,14 +68,14 @@ def find_scripts(root: Path, name_substring: str) -> tuple[list[Path], list[Path
68
68
  return filename_matches, partial_path_matches
69
69
 
70
70
 
71
- def match_file_name(sub_string: str, search_root: PathExtended) -> PathExtended:
71
+ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[str]) -> PathExtended:
72
72
  search_root_obj = search_root.absolute()
73
73
  # assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
74
- filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string)
74
+ filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
75
75
  if len(filename_matches) == 1:
76
76
  return PathExtended(filename_matches[0])
77
- console.print(Panel(f"Partial filename match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
78
- if len(filename_matches) < 10:
77
+ console.print(Panel(f"Partial filename {search_root_obj} match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
78
+ if len(filename_matches) < 20:
79
79
  print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
80
80
  if len(filename_matches) > 1:
81
81
  print("Try to narrow down filename_matches search by case-sensitivity.")
@@ -112,7 +112,7 @@ def match_file_name(sub_string: str, search_root: PathExtended) -> PathExtended:
112
112
  if len(search_res) == 1:
113
113
  return search_root_obj.joinpath(search_res_raw)
114
114
 
115
- print(f"⚠️ WARNING | Multiple search results found for `{sub_string}`\n'{search_res_raw}'")
115
+ print(f"⚠️ WARNING | Multiple search results found for `{sub_string}`:\n'{search_res}'")
116
116
  cmd = f"cd '{search_root_obj}'; fd --type file | fzf --select-1 --query={sub_string}"
117
117
  console.print(Panel(f"🔍 SEARCH STRATEGY | Trying with raw fzf search ...\n{cmd}", title="Search Strategy", expand=False))
118
118
  try:
@@ -1,4 +1,3 @@
1
-
2
1
  from machineconfig.utils.utils2 import randstr
3
2
 
4
3
  from datetime import datetime
@@ -18,13 +17,16 @@ SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
18
17
 
19
18
  def pwd2key(password: str, salt: Optional[bytes] = None, iterations: int = 10) -> bytes: # Derive a secret key from a given password and salt"""
20
19
  import base64
20
+
21
21
  if salt is None:
22
22
  import hashlib
23
+
23
24
  m = hashlib.sha256()
24
25
  m.update(password.encode(encoding="utf-8"))
25
26
  return base64.urlsafe_b64encode(s=m.digest()) # make url-safe bytes required by Ferent.
26
27
  from cryptography.hazmat.primitives import hashes
27
28
  from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
29
+
28
30
  return base64.urlsafe_b64encode(PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=iterations, backend=None).derive(password.encode()))
29
31
 
30
32
 
@@ -394,6 +396,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
394
396
 
395
397
  if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
396
398
  import win32com.shell.shell
399
+
397
400
  _proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
398
401
  # TODO update PATH for this to take effect immediately.
399
402
  time.sleep(1) # wait=True equivalent
@@ -443,6 +446,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
443
446
  if ".zip" in str(slf) and compressed: # the root (self) is itself a zip archive (as opposed to some search results are zip archives)
444
447
  import zipfile
445
448
  import fnmatch
449
+
446
450
  root = slf.as_zip_path()
447
451
  if not r:
448
452
  raw = list(root.iterdir())
@@ -665,6 +669,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
665
669
  def ungz(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
666
670
  op_path = self._resolve_path(folder, name, path, self.name.replace(".gz", "")).expanduser().resolve()
667
671
  import gzip
672
+
668
673
  PathExtended(str(op_path)).write_bytes(gzip.decompress(PathExtended(str(self.expanduser().resolve())).read_bytes()))
669
674
  msg = f"UNGZED {repr(self)} ==> {repr(op_path)}"
670
675
  ret = self if orig else PathExtended(op_path)
@@ -3,12 +3,12 @@ Utils
3
3
  """
4
4
 
5
5
  import machineconfig
6
- import platform
6
+ # import platform
7
7
  from pathlib import Path
8
8
 
9
9
  LIBRARY_ROOT = Path(machineconfig.__file__).resolve().parent
10
10
  REPO_ROOT = LIBRARY_ROOT.parent.parent
11
- PROGRAM_PATH = Path(Path.home().joinpath("tmp_results", "shells", "python_return_command").__str__() + (".ps1" if platform.system() == "Windows" else ".sh"))
11
+ # PROGRAM_PATH = Path(Path.home().joinpath("tmp_results", "shells", "python_return_command").__str__() + (".ps1" if platform.system() == "Windows" else ".sh"))
12
12
  CONFIG_PATH = Path.home().joinpath(".config/machineconfig")
13
13
  DEFAULTS_PATH = Path.home().joinpath("dotfiles/machineconfig/defaults.ini")
14
14
 
@@ -119,13 +119,16 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
119
119
  def view_bar(slf: Any, a: Any, b: Any):
120
120
  slf.total = int(b)
121
121
  slf.update(int(a - slf.n)) # update pbar with increment
122
+
122
123
  from tqdm import tqdm
124
+
123
125
  self.tqdm_wrap = type("TqdmWrap", (tqdm,), {"view_bar": view_bar})
124
126
  self._local_distro: Optional[str] = None
125
127
  self._remote_distro: Optional[str] = None
126
128
  self._remote_machine: Optional[MACHINE] = None
127
129
  self.terminal_responses: list[Response] = []
128
130
  self.platform = platform
131
+
129
132
  def get_remote_machine(self) -> MACHINE:
130
133
  if self._remote_machine is None:
131
134
  if self.run("$env:OS", verbose=False, desc="Testing Remote OS Type").op == "Windows_NT" or self.run("echo %OS%", verbose=False, desc="Testing Remote OS Type Again").op == "Windows_NT":
@@ -138,10 +141,12 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
138
141
  if self._local_distro is None:
139
142
  command = """uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """
140
143
  import subprocess
144
+
141
145
  res = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
142
146
  self._local_distro = res
143
147
  return res
144
148
  return self._local_distro
149
+
145
150
  def get_remote_distro(self):
146
151
  if self._remote_distro is None:
147
152
  res = self.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
@@ -178,8 +183,10 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
178
183
  res = Response(cmd=command)
179
184
  res.output.returncode = os.system(command)
180
185
  return res
186
+
181
187
  def get_ssh_conn_str(self, cmd: str = ""):
182
188
  return "ssh " + (f" -i {self.sshkey}" if self.sshkey else "") + self.get_remote_repr().replace(":", " -p ") + (f" -t {cmd} " if cmd != "" else " ")
189
+
183
190
  def run(self, cmd: str, verbose: bool = True, desc: str = "", strict_err: bool = False, strict_returncode: bool = False) -> Response:
184
191
  raw = self.ssh.exec_command(cmd)
185
192
  res = Response(stdin=raw[0], stdout=raw[1], stderr=raw[2], cmd=cmd, desc=desc) # type: ignore
@@ -189,15 +196,18 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
189
196
  res.print()
190
197
  self.terminal_responses.append(res)
191
198
  return res
199
+
192
200
  def run_py(self, cmd: str, desc: str = "", return_obj: bool = False, verbose: bool = True, strict_err: bool = False, strict_returncode: bool = False) -> Union[Any, Response]:
193
201
  assert '"' not in cmd, 'Avoid using `"` in your command. I dont know how to handle this when passing is as command to python in pwsh command.'
194
202
  if not return_obj:
195
203
  return self.run(
196
- cmd=f"""uv run --with machineconfig -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode)
204
+ cmd=f"""uv run --with machineconfig -c "{Terminal.get_header(wdir=None, toolbox=True)}{cmd}\n""" + '"', desc=desc or f"run_py on {self.get_remote_repr()}", verbose=verbose, strict_err=strict_err, strict_returncode=strict_returncode
205
+ )
197
206
  assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
198
207
  source_file = self.run_py(f"""{cmd}\npath = Save.pickle(obj=obj, path=P.tmpfile(suffix='.pkl'))\nprint(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
199
208
  res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
200
209
  import pickle
210
+
201
211
  return pickle.loads(res.read_bytes())
202
212
 
203
213
  def copy_from_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, overwrite: bool = False, init: bool = True) -> Union[PathExtended, list[PathExtended]]:
@@ -10,7 +10,7 @@ from typing import Any
10
10
  def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
11
11
  """
12
12
  Generate uv add commands for each dependency group in pyproject.toml.
13
-
13
+
14
14
  Args:
15
15
  pyproject_path: Path to the pyproject.toml file
16
16
  output_path: Path where to write the uv add commands
@@ -18,9 +18,9 @@ def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
18
18
  # Read pyproject.toml
19
19
  with open(pyproject_path, "rb") as f:
20
20
  pyproject_data: dict[str, Any] = tomllib.load(f)
21
-
21
+
22
22
  commands: list[str] = []
23
-
23
+
24
24
  # Handle main dependencies (no group)
25
25
  if "project" in pyproject_data and "dependencies" in pyproject_data["project"]:
26
26
  main_deps = pyproject_data["project"]["dependencies"]
@@ -28,7 +28,7 @@ def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
28
28
  # Extract package names without version constraints
29
29
  package_names = [extract_package_name(dep) for dep in main_deps]
30
30
  commands.append(f"uv add {' '.join(package_names)}")
31
-
31
+
32
32
  # Handle optional dependencies as groups
33
33
  if "project" in pyproject_data and "optional-dependencies" in pyproject_data["project"]:
34
34
  optional_deps = pyproject_data["project"]["optional-dependencies"]
@@ -36,7 +36,7 @@ def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
36
36
  if deps:
37
37
  package_names = [extract_package_name(dep) for dep in deps]
38
38
  commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
39
-
39
+
40
40
  # Handle dependency-groups (like dev)
41
41
  if "dependency-groups" in pyproject_data:
42
42
  dep_groups = pyproject_data["dependency-groups"]
@@ -47,19 +47,19 @@ def generate_uv_add_commands(pyproject_path: Path, output_path: Path) -> None:
47
47
  commands.append(f"uv add {' '.join(package_names)} --dev")
48
48
  else:
49
49
  commands.append(f"uv add {' '.join(package_names)} --group {group_name}")
50
-
50
+
51
51
  # Write commands to output file
52
52
  with open(output_path, "w") as f:
53
53
  for command in commands:
54
54
  f.write(command + "\n")
55
-
55
+
56
56
  print(f"Generated {len(commands)} uv add commands in {output_path}")
57
57
 
58
58
 
59
59
  def extract_package_name(dependency_spec: str) -> str:
60
60
  """
61
61
  Extract package name from dependency specification.
62
-
62
+
63
63
  Examples:
64
64
  "rich>=14.0.0" -> "rich"
65
65
  "requests>=2.32.5" -> "requests"
@@ -69,12 +69,12 @@ def extract_package_name(dependency_spec: str) -> str:
69
69
  # Handle extras like "package[extra]>=1.0" first
70
70
  if "[" in dependency_spec:
71
71
  dependency_spec = dependency_spec.split("[")[0].strip()
72
-
72
+
73
73
  # Split on common version operators and take the first part
74
74
  for operator in [">=", "<=", "==", "!=", ">", "<", "~=", "===", "@"]:
75
75
  if operator in dependency_spec:
76
76
  return dependency_spec.split(operator)[0].strip()
77
-
77
+
78
78
  # Return as-is if no version constraint found
79
79
  return dependency_spec.strip()
80
80
 
@@ -84,8 +84,8 @@ if __name__ == "__main__":
84
84
  current_dir = Path.cwd()
85
85
  pyproject_file = current_dir / "pyproject.toml"
86
86
  output_file = current_dir / "uv_add_commands.txt"
87
-
87
+
88
88
  if pyproject_file.exists():
89
89
  generate_uv_add_commands(pyproject_file, output_file)
90
90
  else:
91
- print(f"pyproject.toml not found at {pyproject_file}")
91
+ print(f"pyproject.toml not found at {pyproject_file}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 2.2
3
+ Version: 2.4
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0