machineconfig 7.58__py3-none-any.whl → 7.59__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 (54) hide show
  1. machineconfig/jobs/installer/custom/boxes.py +2 -2
  2. machineconfig/jobs/installer/custom/hx.py +3 -3
  3. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  4. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  5. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
  6. machineconfig/jobs/installer/custom_dev/sysabc.py +1 -20
  7. machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
  8. machineconfig/jobs/installer/installer_data.json +57 -23
  9. machineconfig/jobs/installer/package_groups.py +20 -13
  10. machineconfig/scripts/python/croshell.py +4 -4
  11. machineconfig/scripts/python/devops.py +1 -1
  12. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  13. machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
  14. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +4 -5
  15. machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
  16. machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
  17. machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
  18. machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
  19. machineconfig/scripts/python/helpers_devops/cli_utils.py +0 -72
  20. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  21. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
  22. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
  23. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +3 -2
  24. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  25. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  26. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  27. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
  28. machineconfig/scripts/python/helpers_utils/download.py +151 -0
  29. machineconfig/scripts/python/helpers_utils/path.py +1 -1
  30. machineconfig/scripts/python/interactive.py +2 -2
  31. machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
  32. machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
  33. machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
  34. machineconfig/scripts/python/sessions.py +2 -3
  35. machineconfig/scripts/python/utils.py +2 -1
  36. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  37. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  38. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +10 -10
  39. machineconfig/utils/files/headers.py +2 -2
  40. machineconfig/utils/installer_utils/installer_class.py +38 -24
  41. machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +29 -15
  42. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
  43. machineconfig/utils/options.py +1 -1
  44. machineconfig/utils/path_extended.py +2 -2
  45. machineconfig/utils/path_helper.py +34 -31
  46. machineconfig/utils/ssh.py +1 -1
  47. {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/METADATA +1 -1
  48. {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/RECORD +53 -52
  49. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  50. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  51. /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
  52. {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/WHEEL +0 -0
  53. {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/entry_points.txt +0 -0
  54. {machineconfig-7.58.dist-info → machineconfig-7.59.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
- from machineconfig.utils.path_extended import PathExtended
2
- from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
1
+ from machineconfig.utils.path_extended import PathExtended, DECOMPRESS_SUPPORTED_FORMATS
2
+ from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows
3
3
  from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
4
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
4
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
5
5
  from machineconfig.utils.schemas.installer.installer_types import InstallerData, get_os_name, get_normalized_arch
6
6
 
7
7
  import platform
@@ -11,6 +11,24 @@ from typing import Optional, Any
11
11
  from urllib.parse import urlparse
12
12
 
13
13
 
14
+
15
+ def install_deb_package(downloaded: PathExtended) -> None:
16
+ print(f"📦 Installing .deb package: {downloaded}")
17
+ assert platform.system() == "Linux"
18
+ result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
19
+ success = result.returncode == 0 and result.stderr == ""
20
+ if not success:
21
+ desc = "Installing .deb"
22
+ print(f"❌ {desc} failed")
23
+ if result.stdout:
24
+ print(f"STDOUT: {result.stdout}")
25
+ if result.stderr:
26
+ print(f"STDERR: {result.stderr}")
27
+ print(f"Return code: {result.returncode}")
28
+ print("🗑️ Cleaning up .deb package...")
29
+ downloaded.delete(sure=True)
30
+
31
+
14
32
  class Installer:
15
33
  def __init__(self, installer_data: InstallerData):
16
34
  self.installer_data: InstallerData = installer_data
@@ -106,9 +124,14 @@ class Installer:
106
124
  runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
107
125
  version_to_be_installed = str(version)
108
126
  elif installer_arch_os.startswith("https://"): # its a url to be downloaded
109
- downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
127
+ # downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
128
+ from machineconfig.scripts.python.helpers_utils.download import download
129
+ downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
130
+ if downloaded_object is None:
131
+ raise ValueError(f"Failed to download from URL: {installer_arch_os}")
110
132
  # object is either a zip containing a binary or a straight out binary.
111
- if downloaded_object.suffix in [".zip", ".tar.gz"]:
133
+ downloaded_object = PathExtended(downloaded_object)
134
+ if downloaded_object.suffix in DECOMPRESS_SUPPORTED_FORMATS:
112
135
  downloaded_object = downloaded_object.decompress()
113
136
  if downloaded_object.suffix in [".exe", ""]: # likely an executable
114
137
  if platform.system() == "Windows":
@@ -131,26 +154,17 @@ class Installer:
131
154
  print(f"🔄 Renaming to correct name: {new_exe_name}")
132
155
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
133
156
  version_to_be_installed = "downloaded_binary"
157
+ elif downloaded_object.suffix in [".deb"]:
158
+ install_deb_package(downloaded_object)
159
+ version_to_be_installed = "downloaded_deb"
160
+ else:
161
+ raise ValueError(f"Downloaded file is not an executable: {downloaded_object}")
134
162
  else:
135
163
  raise NotImplementedError(f"CMD installation method not implemented for: {installer_arch_os}")
136
164
  else:
137
165
  assert repo_url.startswith("https://github.com/"), f"repoURL must be a GitHub URL, got {repo_url}"
138
- downloaded, version_to_be_installed = self.download(version=version)
139
- if str(downloaded).endswith(".deb"):
140
- print(f"📦 Installing .deb package: {downloaded}")
141
- assert platform.system() == "Linux"
142
- result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
143
- success = result.returncode == 0 and result.stderr == ""
144
- if not success:
145
- desc = "Installing .deb"
146
- print(f"❌ {desc} failed")
147
- if result.stdout:
148
- print(f"STDOUT: {result.stdout}")
149
- if result.stderr:
150
- print(f"STDERR: {result.stderr}")
151
- print(f"Return code: {result.returncode}")
152
- print("🗑️ Cleaning up .deb package...")
153
- downloaded.delete(sure=True)
166
+ downloaded, version_to_be_installed = self.binary_download(version=version)
167
+ if str(downloaded).endswith(".deb"): install_deb_package(downloaded)
154
168
  else:
155
169
  if platform.system() == "Windows":
156
170
  exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
@@ -173,13 +187,13 @@ class Installer:
173
187
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
174
188
  INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
175
189
  INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed or "unknown", encoding="utf-8")
176
- def download(self, version: Optional[str]) -> tuple[PathExtended, str]:
190
+ def binary_download(self, version: Optional[str]) -> tuple[PathExtended, str]:
177
191
  exe_name = self._get_exe_name()
178
192
  repo_url = self.installer_data["repoURL"]
179
193
  # app_name = self.installer_data["appName"]
180
194
  download_link: Optional[str] = None
181
195
  version_to_be_installed: Optional[str] = None
182
- if "github" not in repo_url or ".zip" in repo_url or ".tar.gz" in repo_url:
196
+ if "github" not in repo_url or (any(ext in repo_url for ext in DECOMPRESS_SUPPORTED_FORMATS)):
183
197
  # Direct download URL
184
198
  download_link = repo_url
185
199
  version_to_be_installed = "predefined_url"
@@ -202,7 +216,7 @@ class Installer:
202
216
  downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
203
217
  if downloaded.is_dir() and len(downloaded.search("*", r=True)) == 1:
204
218
  only_file_in = next(downloaded.glob("*"))
205
- if only_file_in.is_file() and only_file_in.suffix in [".7z", ".zip", ".tar.gz", ".tar"]: # further decompress
219
+ if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
206
220
  downloaded = only_file_in.decompress()
207
221
  return downloaded, version_to_be_installed
208
222
 
@@ -1,19 +1,20 @@
1
1
  """Devops Devapps Install"""
2
2
 
3
3
  import typer
4
- from rich.console import Console
5
- from rich.panel import Panel
6
- from rich.table import Table
7
4
  from typing import Optional, Annotated
8
5
  from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
9
6
 
10
- console = Console()
11
7
 
12
8
 
13
9
  def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
14
10
  """Handle installer not found with friendly suggestions using fuzzy matching."""
15
11
  from difflib import get_close_matches
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.table import Table
16
15
  close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
16
+ console = Console()
17
+
17
18
  console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
18
19
  if close_matches:
19
20
  console.print("🤔 Did you mean one of these?", style="yellow")
@@ -61,6 +62,10 @@ def main(
61
62
  return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
62
63
  else:
63
64
  if group:
65
+ from rich.console import Console
66
+ from rich.table import Table
67
+ console = Console()
68
+
64
69
  typer.echo("❌ You must provide a group name when using the --group/-g option.")
65
70
  res = get_group_name_to_repr()
66
71
  console.print("[bold blue]Here are the available groups:[/bold blue]")
@@ -98,16 +103,16 @@ def get_group_name_to_repr() -> dict[str, str]:
98
103
  def install_interactively():
99
104
  from machineconfig.utils.options import choose_from_options
100
105
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
101
- from machineconfig.utils.installer import get_installers
106
+ from machineconfig.utils.installer_utils.installer_runner import get_installers
102
107
  from machineconfig.utils.installer_utils.installer_class import Installer
108
+ from rich.console import Console
109
+ from rich.panel import Panel
110
+ # from rich.table import Table
103
111
  installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
104
- installer_options = []
105
- for x in installers:
106
- installer_options.append(Installer(installer_data=x).get_description())
107
-
112
+ installer_options = [Installer(installer_data=x).get_description() for x in installers]
108
113
  category_display_to_name = get_group_name_to_repr()
109
114
  options = list(category_display_to_name.keys()) + ["─" * 50] + installer_options
110
- program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", default="📦 termabc", fzf=True)
115
+ program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", fzf=True)
111
116
  installation_messages: list[str] = []
112
117
  for _an_idx, a_program_name in enumerate(program_names):
113
118
  if a_program_name.startswith("─"): # 50 dashes separator
@@ -122,24 +127,32 @@ def install_interactively():
122
127
  status_message = Installer(an_installer_data).install_robust(version=None) # finish the task - this returns a status message, not a command
123
128
  installation_messages.append(status_message)
124
129
  if installation_messages:
130
+ console = Console()
131
+
125
132
  panel = Panel("\n".join([f"[blue]• {message}[/blue]" for message in installation_messages]), title="[bold green]📊 Installation Summary[/bold green]", border_style="green", padding=(1, 2))
126
133
  console.print(panel)
127
134
 
128
135
 
129
136
  def install_group(package_group: str):
130
- from machineconfig.utils.installer import get_installers, install_bulk
137
+ from machineconfig.utils.installer_utils.installer_runner import get_installers, install_bulk
131
138
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
139
+ from rich.console import Console
140
+ from rich.panel import Panel
141
+ # from rich.table import Table
132
142
  if package_group in PACKAGE_GROUP2NAMES:
133
143
  panel = Panel(f"[bold yellow]Installing programs from category: [green]{package_group}[/green][/bold yellow]", title="[bold blue]📦 Category Installation[/bold blue]", border_style="blue", padding=(1, 2))
144
+ console = Console()
134
145
  console.print(panel)
135
146
  installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
136
147
  install_bulk(installers_data=installers_)
137
148
  return
138
- print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
149
+ console = Console()
150
+ console.print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
139
151
  def install_clis(clis_names: list[str]):
140
152
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
141
- from machineconfig.utils.installer import get_installers
153
+ from machineconfig.utils.installer_utils.installer_runner import get_installers
142
154
  from machineconfig.utils.installer_utils.installer_class import Installer
155
+ from rich.console import Console
143
156
  total_messages: list[str] = []
144
157
  for a_which in clis_names:
145
158
  all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
@@ -155,6 +168,7 @@ def install_clis(clis_names: list[str]):
155
168
  message = Installer(selected_installer).install_robust(version=None) # finish the task
156
169
  total_messages.append(message)
157
170
  if total_messages:
171
+ console = Console()
158
172
  console.print("\n[bold green]📊 Installation Results:[/bold green]")
159
173
  for a_message in total_messages:
160
174
  console.print(f"[blue]• {a_message}[/blue]")
@@ -162,13 +176,13 @@ def install_clis(clis_names: list[str]):
162
176
 
163
177
 
164
178
  def install_if_missing(which: str):
165
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
179
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
166
180
  exists = check_tool_exists(which)
167
181
  if exists:
168
182
  print(f"✅ {which} is already installed.")
169
183
  return
170
184
  print(f"⏳ {which} not found. Installing...")
171
- from machineconfig.utils.installer_utils.installer import main
185
+ from machineconfig.utils.installer_utils.installer_cli import main
172
186
  main(which=which, interactive=False)
173
187
 
174
188
 
@@ -1,6 +1,6 @@
1
1
  """package manager"""
2
2
 
3
- from machineconfig.utils.installer_utils.installer_abc import check_if_installed_already
3
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_if_installed_already
4
4
  from machineconfig.utils.installer_utils.installer_class import Installer
5
5
  from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
6
6
  from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
@@ -132,30 +132,6 @@ def install_bulk(installers_data: list[InstallerData], safe: bool = False, jobs:
132
132
  print("✅ Version cache cleared")
133
133
  if safe:
134
134
  pass
135
- # print("⚠️ Safe installation mode activated...")
136
- # from machineconfig.jobs.python.check_installations import APP_SUMMARY_PATH
137
- # if platform.system().lower() == "windows":
138
- # print("🪟 Moving applications to Windows Apps folder...")
139
- # # PathExtended.get_env().WindowsPaths().WindowsApps)
140
- # folder = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps")
141
- # apps_dir.search("*").apply(lambda app: app.move(folder=folder))
142
- # elif platform.system().lower() in ["linux", "darwin"]:
143
- # system_name = "Linux" if platform.system().lower() == "linux" else "macOS"
144
- # print(f"🐧 Moving applications to {system_name} bin folder...")
145
- # if platform.system().lower() == "linux":
146
- # install_path = LINUX_INSTALL_PATH
147
- # else: # Darwin/macOS
148
- # install_path = "/usr/local/bin"
149
- # Terminal().run(f"sudo mv {apps_dir.as_posix()}/* {install_path}/").capture().print_if_unsuccessful(desc=f"MOVING executable to {install_path}", strict_err=True, strict_returncode=True)
150
- # else:
151
- # error_msg = f"❌ ERROR: System {platform.system()} not supported"
152
- # print(error_msg)
153
- # raise NotImplementedError(error_msg)
154
-
155
- # apps_dir.delete(sure=True)
156
- # print(f"✅ Safe installation completed\n{'='*80}")
157
- # return None
158
-
159
135
  print(f"🚀 Starting installation of {len(installers_data)} packages...")
160
136
  print("📦 INSTALLING FIRST PACKAGE 📦")
161
137
  Installer(installers_data[0]).install(version=None)
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
2
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
3
3
  from rich.text import Text
4
4
  from rich.panel import Panel
5
5
  from rich.console import Console
@@ -16,6 +16,7 @@ OPLike: TypeAlias = Union[str, "PathExtended", Path, None]
16
16
  PLike: TypeAlias = Union[str, "PathExtended", Path]
17
17
  FILE_MODE: TypeAlias = Literal["r", "w", "x", "a"]
18
18
  SHUTIL_FORMATS: TypeAlias = Literal["zip", "tar", "gztar", "bztar", "xztar"]
19
+ DECOMPRESS_SUPPORTED_FORMATS = [".tar.gz", ".tgz", ".tar", ".gz", ".tar.bz", ".tbz", ".tar.xz", ".zip", ".7z"]
19
20
 
20
21
 
21
22
  def _is_user_admin() -> bool:
@@ -152,7 +153,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
152
153
  # ======================================= File Editing / Reading ===================================
153
154
  def download(self, folder: OPLike = None, name: Optional[str] = None, allow_redirects: bool = True, timeout: Optional[int] = None, params: Any = None) -> "PathExtended":
154
155
  import requests
155
-
156
156
  response = requests.get(self.as_url_str(), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
157
157
  assert response.status_code == 200, f"Download failed with status code {response.status_code}\n{response.text}"
158
158
  if name is not None:
@@ -809,7 +809,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
809
809
  path = self
810
810
  else:
811
811
  try:
812
- path = self.rel2home()
812
+ path = PathExtended(self.expanduser().absolute().relative_to(Path.home()))
813
813
  except ValueError as ve:
814
814
  if strict:
815
815
  raise ve
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.path_extended import PathExtended
1
+ # from machineconfig.utils.path_extended import Path
2
2
  from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
3
3
  from rich.console import Console
4
4
  from rich.panel import Panel
@@ -10,8 +10,8 @@ from typing import Optional
10
10
  console = Console()
11
11
 
12
12
 
13
- def sanitize_path(a_path: str) -> PathExtended:
14
- path = PathExtended(a_path)
13
+ def sanitize_path(a_path: str) -> Path:
14
+ path = Path(a_path)
15
15
  if Path.cwd() == Path.home() and not path.exists():
16
16
  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"
17
17
  if result == "y":
@@ -22,13 +22,13 @@ def sanitize_path(a_path: str) -> PathExtended:
22
22
  if platform.system() == "Windows": # path copied from Linux/Mac to Windows
23
23
  # For Linux: /home/username, for Mac: /Users/username
24
24
  skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
25
- path = PathExtended.home().joinpath(*path.parts[skip_parts:])
25
+ path = Path.home().joinpath(*path.parts[skip_parts:])
26
26
  assert path.exists(), f"File not found: {path}"
27
27
  source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
28
28
  console.print(Panel(f"🔗 PATH MAPPING | {source_os} → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
29
- elif platform.system() in ["Linux", "Darwin"] and PathExtended.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
29
+ elif platform.system() in ["Linux", "Darwin"] and Path.home().as_posix() not in path.as_posix(): # copied between Unix-like systems with different username
30
30
  skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
31
- path = PathExtended.home().joinpath(*path.parts[skip_parts:])
31
+ path = Path.home().joinpath(*path.parts[skip_parts:])
32
32
  assert path.exists(), f"File not found: {path}"
33
33
  current_os = "Linux" if platform.system() == "Linux" else "macOS"
34
34
  source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
@@ -36,12 +36,12 @@ def sanitize_path(a_path: str) -> PathExtended:
36
36
  elif path.as_posix().startswith("C:"):
37
37
  if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
38
38
  xx = str(a_path).replace("\\\\", "/")
39
- path = PathExtended.home().joinpath(*PathExtended(xx).parts[3:]) # exclude C:\\Users\\username
39
+ path = Path.home().joinpath(*Path(xx).parts[3:]) # exclude C:\\Users\\username
40
40
  assert path.exists(), f"File not found: {path}"
41
41
  target_os = "Linux" if platform.system() == "Linux" else "macOS"
42
42
  console.print(Panel(f"🔗 PATH MAPPING | Windows → {target_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
43
- elif platform.system() == "Windows" and PathExtended.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
44
- path = PathExtended.home().joinpath(*path.parts[2:])
43
+ elif platform.system() == "Windows" and Path.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
44
+ path = Path.home().joinpath(*path.parts[2:])
45
45
  assert path.exists(), f"File not found: {path}"
46
46
  console.print(Panel(f"🔗 PATH MAPPING | Windows → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
47
47
  return path.expanduser().absolute()
@@ -66,12 +66,12 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
66
66
  return filename_matches, partial_path_matches
67
67
 
68
68
 
69
- def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[str]) -> PathExtended:
69
+ def match_file_name(sub_string: str, search_root: Path, suffixes: set[str]) -> Path:
70
70
  search_root_obj = search_root.absolute()
71
71
  # assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
72
72
  filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
73
73
  if len(filename_matches) == 1:
74
- return PathExtended(filename_matches[0])
74
+ return Path(filename_matches[0])
75
75
  console.print(Panel(f"Partial filename {search_root_obj} match with case-insensitivity failed. This generated #{len(filename_matches)} results.", title="Search", expand=False))
76
76
  if len(filename_matches) < 20:
77
77
  print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
@@ -80,23 +80,23 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
80
80
  # let's see if avoiding .lower() helps narrowing down to one result
81
81
  reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
82
82
  if len(reduced_scripts) == 1:
83
- return PathExtended(reduced_scripts[0])
83
+ return Path(reduced_scripts[0])
84
84
  elif len(reduced_scripts) > 1:
85
85
  from machineconfig.utils.options import choose_from_options
86
86
  choice = choose_from_options(multi=False, msg="Multiple matches found", options=reduced_scripts, fzf=True)
87
- return PathExtended(choice)
87
+ return Path(choice)
88
88
  print(f"Result: This still generated {len(reduced_scripts)} results.")
89
89
  if len(reduced_scripts) < 10:
90
90
  print("\n".join([a_potential_match.as_posix() for a_potential_match in reduced_scripts]))
91
91
 
92
92
  console.print(Panel(f"Partial path match with case-insensitivity failed. This generated #{len(partial_path_matches)} results.", title="Search", expand=False))
93
93
  if len(partial_path_matches) == 1:
94
- return PathExtended(partial_path_matches[0])
94
+ return Path(partial_path_matches[0])
95
95
  elif len(partial_path_matches) > 1:
96
96
  print("Try to narrow down partial_path_matches search by case-sensitivity.")
97
97
  reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
98
98
  if len(reduced_scripts) == 1:
99
- return PathExtended(reduced_scripts[0])
99
+ return Path(reduced_scripts[0])
100
100
  print(f"Result: This still generated {len(reduced_scripts)} results.")
101
101
 
102
102
  try:
@@ -131,21 +131,24 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
131
131
  return search_root_obj.joinpath(res)
132
132
 
133
133
 
134
- def search_for_files_of_interest(path_obj: PathExtended, suffixes: set[str]):
135
- if path_obj.joinpath(".venv").exists():
136
- path_objects = path_obj.search("*", not_in=[".venv"])
137
- files: list[PathExtended] = []
138
- for a_path_obj in path_objects:
139
- files += search_for_files_of_interest(path_obj=a_path_obj, suffixes=suffixes)
140
- return files
134
+ def search_for_files_of_interest(path_obj: Path, suffixes: set[str]) -> list[Path]:
141
135
  if path_obj.is_file():
142
136
  return [path_obj]
143
- files: list[PathExtended] = []
144
- for a_suffix in suffixes:
145
- if a_suffix == ".py":
146
- files += path_obj.search(pattern="*.py", r=True, not_in=["__init__.py"])
147
- else:
148
- files += path_obj.search(pattern=f"*{a_suffix}", r=True)
137
+ files: list[Path] = []
138
+ directories_to_visit: list[Path] = [path_obj]
139
+ while directories_to_visit:
140
+ current_dir = directories_to_visit.pop()
141
+ for entry in current_dir.iterdir():
142
+ if entry.is_dir():
143
+ if entry.name == ".venv":
144
+ continue
145
+ directories_to_visit.append(entry)
146
+ continue
147
+ if entry.suffix not in suffixes:
148
+ continue
149
+ if entry.suffix == ".py" and entry.name == "__init__.py":
150
+ continue
151
+ files.append(entry)
149
152
  return files
150
153
 
151
154
 
@@ -160,15 +163,15 @@ def get_choice_file(path: str, suffixes: Optional[set[str]]):
160
163
  else:
161
164
  suffixes = {".py"}
162
165
  if not path_obj.exists():
163
- print(f"🔍 Searching for file matching `{path}` under `{PathExtended.cwd()}`, but only if suffix matches {suffixes}")
164
- choice_file = match_file_name(sub_string=path, search_root=PathExtended.cwd(), suffixes=suffixes)
166
+ print(f"🔍 Searching for file matching `{path}` under `{Path.cwd()}`, but only if suffix matches {suffixes}")
167
+ choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
165
168
  elif path_obj.is_dir():
166
169
  print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
167
170
  files = search_for_files_of_interest(path_obj, suffixes=suffixes)
168
171
  print(f"🔍 Got #{len(files)} results.")
169
172
  from machineconfig.utils.options import choose_from_options
170
173
  choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
171
- choice_file = PathExtended(choice_file)
174
+ choice_file = Path(choice_file)
172
175
  else:
173
176
  choice_file = path_obj
174
177
  return choice_file
@@ -8,7 +8,7 @@ from machineconfig.utils.terminal import Response
8
8
  from machineconfig.utils.accessories import pprint, randstr
9
9
  from machineconfig.utils.meta import lambda_to_python_script
10
10
  UV_RUN_CMD = "$HOME/.local/bin/uv run" if platform.system() != "Windows" else """& "$env:USERPROFILE/.local/bin/uv" run"""
11
- MACHINECONFIG_VERSION = "machineconfig>=7.58"
11
+ MACHINECONFIG_VERSION = "machineconfig>=7.59"
12
12
  DEFAULT_PICKLE_SUBDIR = "tmp_results/tmp_scripts/ssh"
13
13
 
14
14
  class SSH:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 7.58
3
+ Version: 7.59
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0