machineconfig 5.25__py3-none-any.whl → 5.26__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 (48) hide show
  1. machineconfig/jobs/installer/custom/hx.py +1 -1
  2. machineconfig/jobs/installer/custom_dev/alacritty.py +4 -4
  3. machineconfig/jobs/installer/linux_scripts/wezterm.sh +1 -1
  4. machineconfig/scripts/python/agents.py +4 -4
  5. machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
  6. machineconfig/scripts/python/devops.py +6 -6
  7. machineconfig/scripts/python/{devops_update_repos.py → devops_helpers/devops_update_repos.py} +1 -1
  8. machineconfig/scripts/python/fire_jobs.py +3 -3
  9. machineconfig/scripts/python/helpers/repo_sync_helpers.py +14 -1
  10. machineconfig/scripts/python/helpers_fire/__init__.py +0 -0
  11. machineconfig/scripts/python/{fire_agents_help_launch.py → helpers_fire/fire_agents_help_launch.py} +1 -1
  12. machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
  13. machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
  14. machineconfig/scripts/python/helpers_repos/grource.py +341 -0
  15. machineconfig/scripts/python/interactive.py +1 -1
  16. machineconfig/scripts/python/repos.py +75 -73
  17. machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py} +1 -1
  18. machineconfig/scripts/python/{repos_helper.py → repos_helpers/repos_helper.py} +4 -12
  19. machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py} +1 -1
  20. machineconfig/scripts/python/sessions_multiprocess.py +1 -1
  21. machineconfig/utils/files/ouch/__init__.py +0 -0
  22. machineconfig/utils/files/ouch/decompress.py +45 -0
  23. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  24. machineconfig/utils/source_of_truth.py +0 -1
  25. machineconfig/utils/ssh.py +33 -19
  26. {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/METADATA +3 -1
  27. {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/RECORD +46 -42
  28. machineconfig/scripts/python/get_zellij_cmd.py +0 -15
  29. machineconfig/scripts/python/t4.py +0 -17
  30. /machineconfig/jobs/{python → installer}/check_installations.py +0 -0
  31. /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → devops_helpers/__init__.py} +0 -0
  32. /machineconfig/scripts/python/{devops_add_identity.py → devops_helpers/devops_add_identity.py} +0 -0
  33. /machineconfig/scripts/python/{devops_add_ssh_key.py → devops_helpers/devops_add_ssh_key.py} +0 -0
  34. /machineconfig/scripts/python/{devops_backup_retrieve.py → devops_helpers/devops_backup_retrieve.py} +0 -0
  35. /machineconfig/scripts/python/{devops_status.py → devops_helpers/devops_status.py} +0 -0
  36. /machineconfig/scripts/python/{fire_agents_help_search.py → helpers_fire/fire_agents_help_search.py} +0 -0
  37. /machineconfig/scripts/python/{fire_agents_helper_types.py → helpers_fire/fire_agents_helper_types.py} +0 -0
  38. /machineconfig/scripts/python/{fire_agents_load_balancer.py → helpers_fire/fire_agents_load_balancer.py} +0 -0
  39. /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
  40. /machineconfig/scripts/python/{fire_jobs_args_helper.py → helpers_fire_command/fire_jobs_args_helper.py} +0 -0
  41. /machineconfig/scripts/python/{fire_jobs_route_helper.py → helpers_fire_command/fire_jobs_route_helper.py} +0 -0
  42. /machineconfig/scripts/python/{count_lines.py → repos_helpers/count_lines.py} +0 -0
  43. /machineconfig/scripts/python/{repos_helper_clone.py → repos_helpers/repos_helper_clone.py} +0 -0
  44. /machineconfig/scripts/python/{repos_helper_record.py → repos_helpers/repos_helper_record.py} +0 -0
  45. /machineconfig/scripts/python/{repos_helper_update.py → repos_helpers/repos_helper_update.py} +0 -0
  46. {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/WHEEL +0 -0
  47. {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/entry_points.txt +0 -0
  48. {machineconfig-5.25.dist-info → machineconfig-5.26.dist-info}/top_level.txt +0 -0
@@ -5,128 +5,130 @@ in the event that username@github.com is not mentioned in the remote url.
5
5
 
6
6
  """
7
7
 
8
-
8
+ from pathlib import Path
9
9
  from typing import Annotated, Optional
10
10
  import typer
11
11
 
12
12
 
13
-
14
13
  app = typer.Typer(help="� Manage development repositories", no_args_is_help=True)
15
14
  sync_app = typer.Typer(help="� Manage repository specifications and syncing", no_args_is_help=True)
16
15
  app.add_typer(sync_app, name="sync", help="� Sync repositories using saved specs")
17
16
 
18
17
 
19
- DirectoryArgument = Annotated[
20
- Optional[str],
21
- typer.Argument(help="📁 Folder containing repos or the specs JSON file to use."),
22
- ]
23
- RecursiveOption = Annotated[
24
- bool,
25
- typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories."),
26
- ]
27
- NoSyncOption = Annotated[
28
- bool,
29
- typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls."),
30
- ]
31
- CloudOption = Annotated[
32
- Optional[str],
33
- typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote."),
34
- ]
35
-
18
+ DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Folder containing repos or the specs JSON file to use.")]
19
+ RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
20
+ NoSyncOption = Annotated[bool, typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls.")]
21
+ CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
36
22
 
37
23
 
38
24
  @app.command(no_args_is_help=True)
39
- def push(directory: DirectoryArgument = None,
40
- recursive: RecursiveOption = False,
41
- no_sync: NoSyncOption = False,
42
- ) -> None:
25
+ def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
43
26
  """🚀 Push changes across repositories."""
44
- from machineconfig.scripts.python.repos_helper import git_operations
27
+ from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
28
+
45
29
  git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, no_sync=no_sync)
30
+
31
+
46
32
  @app.command(no_args_is_help=True)
47
- def pull(
48
- directory: DirectoryArgument = None,
49
- recursive: RecursiveOption = False,
50
- no_sync: NoSyncOption = False,
51
- ) -> None:
33
+ def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
52
34
  """⬇️ Pull changes across repositories."""
53
- from machineconfig.scripts.python.repos_helper import git_operations
35
+ from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
36
+
54
37
  git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, no_sync=no_sync)
38
+
39
+
55
40
  @app.command(no_args_is_help=True)
56
- def commit(
57
- directory: DirectoryArgument = None,
58
- recursive: RecursiveOption = False,
59
- no_sync: NoSyncOption = False,
60
- ) -> None:
41
+ def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
61
42
  """💾 Commit changes across repositories."""
62
- from machineconfig.scripts.python.repos_helper import git_operations
43
+ from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
44
+
63
45
  git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, no_sync=no_sync)
46
+
47
+
64
48
  @app.command(no_args_is_help=True)
65
- def cleanup(
66
- directory: DirectoryArgument = None,
67
- recursive: RecursiveOption = False,
68
- no_sync: NoSyncOption = False,
69
- ) -> None:
49
+ def cleanup(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
70
50
  """🔄 Pull, commit, and push changes across repositories."""
71
- from machineconfig.scripts.python.repos_helper import git_operations
51
+ from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
52
+
72
53
  git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, no_sync=no_sync)
73
54
 
74
55
 
75
56
  @sync_app.command(no_args_is_help=True)
76
- def capture(
77
- directory: DirectoryArgument = None,
78
- cloud: CloudOption = None,
79
- ) -> None:
57
+ def capture(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
80
58
  """📝 Record repositories into a repos.json specification."""
81
- from machineconfig.scripts.python.repos_helper import print_banner, resolve_directory
82
- print_banner()
59
+ from machineconfig.scripts.python.repos_helpers.repos_helper import resolve_directory
83
60
  repos_root = resolve_directory(directory)
84
- from machineconfig.scripts.python.repos_helper_record import main as record_repos
61
+ from machineconfig.scripts.python.repos_helpers.repos_helper_record import main as record_repos
62
+
85
63
  save_path = record_repos(repos_root=repos_root)
86
64
  from machineconfig.utils.path_extended import PathExtended
65
+
87
66
  if cloud is not None:
88
67
  PathExtended(save_path).to_cloud(rel2home=True, cloud=cloud)
68
+
69
+
89
70
  @sync_app.command(no_args_is_help=True)
90
- def clone(
91
- directory: DirectoryArgument = None,
92
- cloud: CloudOption = None,
93
- ) -> None:
71
+ def clone(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
94
72
  """📥 Clone repositories described by a repos.json specification."""
95
- from machineconfig.scripts.python.repos_helper import print_banner, clone_from_specs
96
- print_banner()
73
+ from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
74
+
75
+
97
76
  clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=False)
98
77
 
99
78
 
100
79
  @sync_app.command(name="checkout-to-commit", no_args_is_help=True)
101
- def checkout_command(
102
- directory: DirectoryArgument = None,
103
- cloud: CloudOption = None,
104
- ) -> None:
80
+ def checkout_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
105
81
  """🔀 Check out specific commits listed in the specification."""
106
- from machineconfig.scripts.python.repos_helper import print_banner, clone_from_specs
107
- print_banner()
82
+ from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
83
+
84
+
108
85
  clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=True)
109
86
 
110
87
 
111
88
  @sync_app.command(name="checkout-to-branch", no_args_is_help=True)
112
- def checkout_to_branch_command(
113
- directory: DirectoryArgument = None,
114
- cloud: CloudOption = None,
115
- ) -> None:
89
+ def checkout_to_branch_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
116
90
  """🔀 Check out to the main branch defined in the specification."""
117
- from machineconfig.scripts.python.repos_helper import print_banner, clone_from_specs
118
- print_banner()
91
+ from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
119
92
  clone_from_specs(directory, cloud, checkout_branch_flag=True, checkout_commit_flag=False)
120
93
 
121
94
 
122
95
  @app.command(no_args_is_help=True)
123
- def analyze(
124
- directory: DirectoryArgument = None,
125
- ) -> None:
96
+ def analyze(directory: DirectoryArgument = None) -> None:
126
97
  """📊 Analyze repository development over time."""
127
- from machineconfig.scripts.python.repos_helper import print_banner
128
- print_banner()
129
98
  repo_path = directory if directory is not None else "."
130
- from machineconfig.scripts.python.count_lines_frontend import analyze_repo_development
99
+ from machineconfig.scripts.python.repos_helpers.count_lines_frontend import analyze_repo_development
100
+
131
101
  analyze_repo_development(repo_path=repo_path)
132
102
 
103
+
104
+ @app.command(no_args_is_help=True)
105
+ def viz(
106
+ repo: str = typer.Option(Path.cwd().__str__(), "--repo", "-r", help="Path to git repository to visualize"),
107
+ output_file: Optional[Path] = typer.Option(None, "--output", "-o", help="Output video file (e.g., output.mp4). If specified, gource will render to video."),
108
+ resolution: str = typer.Option("1920x1080", "--resolution", "-res", help="Video resolution (e.g., 1920x1080, 1280x720)"),
109
+ seconds_per_day: float = typer.Option(0.1, "--seconds-per-day", "-spd", help="Speed of simulation (lower = faster)"),
110
+ auto_skip_seconds: float = typer.Option(1.0, "--auto-skip-seconds", "-as", help="Skip to next entry if nothing happens for X seconds"),
111
+ title: Optional[str] = typer.Option(None, "--title", "-t", help="Title for the visualization"),
112
+ hide_items: list[str] = typer.Option([], "--hide", "-h", help="Items to hide: bloom, date, dirnames, files, filenames, mouse, progress, root, tree, users, usernames"),
113
+ key_items: bool = typer.Option(False, "--key", "-k", help="Show file extension key"),
114
+ fullscreen: bool = typer.Option(False, "--fullscreen", "-f", help="Run in fullscreen mode"),
115
+ viewport: Optional[str] = typer.Option(None, "--viewport", "-v", help="Camera viewport (e.g., '1000x1000')"),
116
+ start_date: Optional[str] = typer.Option(None, "--start-date", help="Start date (YYYY-MM-DD)"),
117
+ stop_date: Optional[str] = typer.Option(None, "--stop-date", help="Stop date (YYYY-MM-DD)"),
118
+ user_image_dir: Optional[Path] = typer.Option(None, "--user-image-dir", help="Directory with user avatar images"),
119
+ max_files: int = typer.Option(0, "--max-files", help="Maximum number of files to show (0 = no limit)"),
120
+ max_file_lag: float = typer.Option(5.0, "--max-file-lag", help="Max time files remain on screen after last change"),
121
+ file_idle_time: int = typer.Option(0, "--file-idle-time", help="Time in seconds files remain idle before being removed"),
122
+ framerate: int = typer.Option(60, "--framerate", help="Frames per second for video output"),
123
+ background_color: str = typer.Option("000000", "--background-color", help="Background color in hex (e.g., 000000 for black)"),
124
+ font_size: int = typer.Option(22, "--font-size", help="Font size"),
125
+ camera_mode: str = typer.Option("overview", "--camera-mode", help="Camera mode: overview or track"),
126
+ ) -> None:
127
+ """🎬 Visualize repository activity using Gource."""
128
+ from machineconfig.scripts.python.helpers_repos.grource import visualize
129
+ visualize(repo=repo, output_file=output_file, resolution=resolution, seconds_per_day=seconds_per_day,
130
+ auto_skip_seconds=auto_skip_seconds, title=title, hide_items=hide_items, key_items=key_items,
131
+ fullscreen=fullscreen, viewport=viewport, start_date=start_date, stop_date=stop_date,
132
+ user_image_dir=user_image_dir, max_files=max_files, max_file_lag=max_file_lag,
133
+ file_idle_time=file_idle_time, framerate=framerate, background_color=background_color,
134
+ font_size=font_size, camera_mode=camera_mode)
@@ -3,7 +3,7 @@ import typer
3
3
 
4
4
 
5
5
  def analyze_repo_development(repo_path: str = typer.Argument(..., help="Path to the git repository")):
6
- from machineconfig.scripts.python import count_lines
6
+ from machineconfig.scripts.python.repos_helpers import count_lines
7
7
  from pathlib import Path
8
8
  count_lines_path = Path(count_lines.__file__)
9
9
  # --project $HOME/code/machineconfig --group plot
@@ -8,14 +8,6 @@ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
8
8
  import typer
9
9
 
10
10
 
11
-
12
- def print_banner() -> None:
13
- typer.echo("\n" + "=" * 50)
14
- typer.echo("📂 Welcome to the Repository Manager")
15
- typer.echo("=" * 50 + "\n")
16
-
17
-
18
-
19
11
  def resolve_directory(directory: Optional[str]) -> Path:
20
12
  if directory is None:
21
13
  directory = Path.cwd().as_posix()
@@ -30,10 +22,10 @@ def git_operations(
30
22
  recursive: bool,
31
23
  no_sync: bool,
32
24
  ) -> None:
33
- print_banner()
25
+
34
26
  repos_root = resolve_directory(directory)
35
27
  auto_sync = not no_sync
36
- from machineconfig.scripts.python.repos_helper_action import perform_git_operations
28
+ from machineconfig.scripts.python.repos_helpers.repos_helper_action import perform_git_operations
37
29
  from machineconfig.utils.path_extended import PathExtended
38
30
  perform_git_operations(
39
31
  repos_root=PathExtended(repos_root),
@@ -73,10 +65,10 @@ def clone_from_specs(
73
65
  checkout_branch_flag: bool,
74
66
  checkout_commit_flag: bool,
75
67
  ) -> None:
76
- print_banner()
68
+
77
69
  typer.echo("\n📥 Cloning or checking out repositories...")
78
70
  spec_path = resolve_spec_path(directory, cloud)
79
- from machineconfig.scripts.python.repos_helper_clone import clone_repos
71
+ from machineconfig.scripts.python.repos_helpers.repos_helper_clone import clone_repos
80
72
  clone_repos(
81
73
  spec_path=spec_path,
82
74
  preferred_remote=None,
@@ -1,6 +1,6 @@
1
1
  from machineconfig.utils.path_extended import PathExtended
2
2
  from machineconfig.utils.accessories import randstr
3
- from machineconfig.scripts.python.repos_helper_update import update_repository
3
+ from machineconfig.scripts.python.repos_helpers.repos_helper_update import update_repository
4
4
 
5
5
  from typing import Optional
6
6
  from dataclasses import dataclass
@@ -41,7 +41,7 @@ def create_from_function(
41
41
 
42
42
  # ========================= choosing function to run
43
43
  if function is None or function.strip() == "":
44
- from machineconfig.scripts.python.fire_jobs_route_helper import choose_function_or_lines
44
+ from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
45
45
  choice_function, choice_file, _kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict={})
46
46
  else:
47
47
  choice_function = function
File without changes
@@ -0,0 +1,45 @@
1
+
2
+
3
+ from pathlib import Path
4
+ from zipfile import ZipFile, BadZipFile
5
+ from typing import Union
6
+
7
+
8
+ def decompress_and_remove_zip(zip_path: Union[str, Path]) -> None:
9
+ """
10
+ Decompress a ZIP file and remove it after extraction.
11
+
12
+ Args:
13
+ zip_path (Union[str, Path]): Path to the ZIP file.
14
+
15
+ Raises:
16
+ FileNotFoundError: If the zip file does not exist.
17
+ BadZipFile: If the file is not a valid ZIP archive.
18
+ PermissionError: If the file cannot be deleted due to permission issues.
19
+ Exception: For other unexpected errors.
20
+ """
21
+ zip_path = Path(zip_path)
22
+
23
+ if not zip_path.exists():
24
+ raise FileNotFoundError(f"The file '{zip_path}' does not exist.")
25
+
26
+ if not zip_path.is_file():
27
+ raise FileNotFoundError(f"The path '{zip_path}' is not a file.")
28
+
29
+ extract_dir = zip_path.parent
30
+
31
+ try:
32
+ with ZipFile(zip_path, 'r') as zip_ref:
33
+ zip_ref.extractall(extract_dir)
34
+ except BadZipFile as e:
35
+ raise BadZipFile(f"The file '{zip_path}' is not a valid zip archive.") from e
36
+ except Exception as e:
37
+ raise Exception(f"Failed to extract '{zip_path}': {e}") from e
38
+
39
+ try:
40
+ zip_path.unlink()
41
+ except PermissionError as e:
42
+ raise PermissionError(f"Permission denied when deleting '{zip_path}'.") from e
43
+ except Exception as e:
44
+ raise Exception(f"Failed to delete '{zip_path}': {e}") from e
45
+
@@ -6,7 +6,7 @@ capturing all user inputs collected during interactive execution.
6
6
 
7
7
  from pathlib import Path
8
8
  from typing import TypedDict, Literal, NotRequired
9
- from machineconfig.scripts.python.fire_agents_help_launch import AGENTS
9
+ from machineconfig.scripts.python.helpers_fire.fire_agents_help_launch import AGENTS
10
10
 
11
11
  SEARCH_STRATEGIES = Literal["file_path", "keyword_search", "filename_pattern"]
12
12
 
@@ -17,7 +17,6 @@ INSTALL_VERSION_ROOT = CONFIG_PATH.joinpath("cli_tools_installers/versions")
17
17
  INSTALL_TMP_DIR = Path.home().joinpath("tmp_results", "tmp_installers")
18
18
 
19
19
  # LINUX_INSTALL_PATH = '/usr/local/bin'
20
- # LINUX_INSTALL_PATH = '~/.local/bin'
21
20
  LINUX_INSTALL_PATH = Path.home().joinpath(".local/bin").__str__()
22
21
  WINDOWS_INSTALL_PATH = Path.home().joinpath("AppData/Local/Microsoft/WindowsApps").__str__()
23
22
 
@@ -8,12 +8,7 @@ from machineconfig.utils.accessories import pprint
8
8
  # from machineconfig.utils.ve import get_ve_activate_line
9
9
 
10
10
 
11
- def get_header(wdir: OPLike):
12
- return f"""
13
- # >> Code prepended
14
- {'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
15
- # >> End of header, start of script passed
16
- """
11
+ def get_header(wdir: str): return f"""import sys; sys.path.insert(0, r'{wdir}')"""
17
12
 
18
13
 
19
14
  @dataclass
@@ -165,7 +160,6 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
165
160
  if self._local_distro is None:
166
161
  command = """uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """
167
162
  import subprocess
168
-
169
163
  res = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
170
164
  self._local_distro = res
171
165
  return res
@@ -173,7 +167,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
173
167
 
174
168
  def get_remote_distro(self):
175
169
  if self._remote_distro is None:
176
- res = self.run("""~/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
170
+ res = self.run("""$HOME/.local/bin/uv run --with distro python -c "import distro; print(distro.name(pretty=True))" """)
177
171
  self._remote_distro = res.op_if_successfull_or_default() or ""
178
172
  return self._remote_distro
179
173
 
@@ -225,17 +219,22 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
225
219
  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.'
226
220
  if not return_obj:
227
221
  return self.run(
228
- cmd=f"""uv run --no-dev --project $HOME/code/machineconfig -c "{get_header(wdir=None)}{cmd}\n""" + '"',
222
+ cmd=f"""$HOME/.local/bin/uv run --no-dev --project $HOME/code/machineconfig python -c "{cmd}\n""" + '"',
229
223
  desc=desc or f"run_py on {self.get_remote_repr()}",
230
224
  verbose=verbose,
231
225
  strict_err=strict_err,
232
226
  strict_returncode=strict_returncode,
233
227
  )
234
228
  assert "obj=" in cmd, "The command sent to run_py must have `obj=` statement if return_obj is set to True"
235
- 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]
229
+ source_file = self.run_py(f"""{cmd}
230
+ import pickle
231
+ import tempfile
232
+ from pathlib import Path
233
+ path = tempfile.emkstemp(suffix=".pkl")[1]
234
+ Path(path).write_bytes(pickle.dumps(obj))
235
+ print(path)""", desc=desc, verbose=verbose, strict_err=True, strict_returncode=True).op.split("\n")[-1]
236
236
  res = self.copy_to_here(source=source_file, target=PathExtended.tmpfile(suffix=".pkl"))
237
237
  import pickle
238
-
239
238
  return pickle.loads(res.read_bytes())
240
239
 
241
240
  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]]:
@@ -255,7 +254,11 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
255
254
  source_list: list[PathExtended] = source_obj.search("*", folders=False, files=True, r=True)
256
255
  remote_root = (
257
256
  self.run_py(
258
- f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.create())",
257
+ f"""
258
+ from machineconfig.utils.path_extended import PathExtended as P
259
+ path=P(r'{PathExtended(target).as_posix()}').expanduser()
260
+ {'path.delete(sure=True)' if overwrite else ''}
261
+ print(path.create())""",
259
262
  desc=f"Creating Target directory `{PathExtended(target).as_posix()}` @ {self.get_remote_repr()}",
260
263
  verbose=False,
261
264
  ).op
@@ -272,7 +275,10 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
272
275
  source_obj = PathExtended(source_obj).expanduser().zip(content=True) # .append(f"_{randstr()}", inplace=True) # eventually, unzip will raise content flag, so this name doesn't matter.
273
276
  remotepath = (
274
277
  self.run_py(
275
- f"path=P(r'{PathExtended(target).as_posix()}').expanduser()\n{'path.delete(sure=True)' if overwrite else ''}\nprint(path.parent.create())",
278
+ f"""
279
+ path=P(r'{PathExtended(target).as_posix()}').expanduser()
280
+ {'path.delete(sure=True)' if overwrite else ''}
281
+ print(path.parent.create())""",
276
282
  desc=f"Creating Target directory `{PathExtended(target).parent.as_posix()}` @ {self.get_remote_repr()}",
277
283
  verbose=False,
278
284
  ).op
@@ -283,7 +289,9 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
283
289
  with self.tqdm_wrap(ascii=True, unit="b", unit_scale=True) as pbar:
284
290
  self.sftp.put(localpath=PathExtended(source_obj).expanduser(), remotepath=remotepath.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
285
291
  if z:
286
- _resp = self.run_py(f"""P(r'{remotepath.as_posix()}').expanduser().unzip(content=False, inplace=True, overwrite={overwrite})""", desc=f"UNZIPPING {remotepath.as_posix()}", verbose=False, strict_err=True, strict_returncode=True)
292
+ _resp = self.run_py(f"""
293
+ from machineconfig.utils.path_extended import PathExtended as P;
294
+ P(r'{remotepath.as_posix()}').expanduser().unzip(content=False, inplace=True, overwrite={overwrite})""", desc=f"UNZIPPING {remotepath.as_posix()}", verbose=False, strict_err=True, strict_returncode=True)
287
295
  source_obj.delete(sure=True)
288
296
  print("\n")
289
297
  return source_obj
@@ -291,15 +299,20 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
291
299
  def copy_to_here(self, source: PLike, target: OPLike = None, z: bool = False, r: bool = False, init: bool = True) -> PathExtended:
292
300
  if init:
293
301
  print(f"{'⬇️' * 5} SFTP DOWNLOADING FROM `{source}` TO `{target}`")
294
- if not z and self.run_py(f"print(P(r'{source}').expanduser().absolute().is_dir())", desc=f"Check if source `{source}` is a dir", verbose=False, strict_returncode=True, strict_err=True).op.split("\n")[-1] == "True":
302
+ if not z and self.run_py(f"""
303
+ from machineconfig.utils.path_extended import PathExtended as P
304
+ print(P(r'{source}').expanduser().absolute().is_dir())""", desc=f"Check if source `{source}` is a dir", verbose=False, strict_returncode=True, strict_err=True).op.split("\n")[-1] == "True":
295
305
  if r is False:
296
306
  raise RuntimeError(f"source `{source}` is a directory! either set r=True for recursive sending or raise zip_first flag.")
297
- source_list = self.run_py(f"obj=P(r'{source}').search(folders=False, r=True).collapseuser(strict=False)", desc="Searching for files in source", return_obj=True, verbose=False)
307
+ source_list = self.run_py(f"""
308
+ from machineconfig.utils.path_extended import PathExtended as P
309
+ obj=P(r'{source}').search(folders=False, r=True).collapseuser(strict=False)
310
+ """, desc="Searching for files in source", return_obj=True, verbose=False)
298
311
  assert isinstance(source_list, List), f"Could not resolve source path {source} due to error"
299
312
  for file in source_list:
300
313
  self.copy_to_here(source=file.as_posix(), target=PathExtended(target).joinpath(PathExtended(file).relative_to(source)) if target else None, r=False)
301
314
  if z:
302
- tmp: Response = self.run_py(f"print(P(r'{source}').expanduser().zip(inplace=False, verbose=False))", desc=f"Zipping source file {source}", verbose=False)
315
+ tmp: Response = self.run_py(f"from machineconfig.utils.path_extended import PathExtended as P; print(P(r'{source}').expanduser().zip(inplace=False, verbose=False))", desc=f"Zipping source file {source}", verbose=False)
303
316
  tmp2 = tmp.op2path(strict_returncode=True, strict_err=True)
304
317
  if not isinstance(tmp2, PathExtended):
305
318
  raise RuntimeError(f"Could not zip {source} due to {tmp.err}")
@@ -330,7 +343,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
330
343
  self.sftp.get(remotepath=source.as_posix(), localpath=str(target_obj), callback=pbar.view_bar) # type: ignore
331
344
  if z:
332
345
  target_obj = target_obj.unzip(inplace=True, content=True)
333
- self.run_py(f"P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True, verbose=False)
346
+ self.run_py(f"from machineconfig.utils.path_extended import PathExtended as P; P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True, verbose=False)
334
347
  print("\n")
335
348
  return target_obj
336
349
 
@@ -355,6 +368,7 @@ class SSH: # inferior alternative: https://github.com/fabric/fabric
355
368
  self.sftp.get(remotepath=source.as_posix(), localpath=target.as_posix(), callback=pbar.view_bar) # type: ignore # pylint: disable=E1129
356
369
  if z:
357
370
  target = target.unzip(inplace=True, content=True)
358
- self.run_py(f"P(r'{source.as_posix()}').delete(sure=True)", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True)
371
+ self.run_py(f"""
372
+ from machineconfig.utils.path_extended import PathExtended as P; P(r'{source.as_posix()}').delete(sure=True)""", desc="Cleaning temp zip files @ remote.", strict_returncode=True, strict_err=True)
359
373
  print("\n")
360
374
  return target
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 5.25
3
+ Version: 5.26
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -60,11 +60,13 @@ Additionally, files that contain data, sensitive information that should not be
60
60
 
61
61
 
62
62
  # Windows:
63
+
63
64
  ```powershell
64
65
  iex (iwr bit.ly/cfgiawindows).Content
65
66
  ```
66
67
 
67
68
  # Linux and MacOS
69
+
68
70
  ```bash
69
71
  . <(curl -sL bit.ly/cfgialinux)
70
72
  ```