machineconfig 4.94__py3-none-any.whl → 4.97__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 (39) hide show
  1. machineconfig/cluster/sessions_managers/ffile.py +4 -0
  2. machineconfig/jobs/installer/custom/gh.py +2 -15
  3. machineconfig/jobs/installer/custom_dev/alacritty.py +41 -26
  4. machineconfig/jobs/installer/custom_dev/brave.py +42 -28
  5. machineconfig/jobs/installer/custom_dev/bypass_paywall.py +30 -19
  6. machineconfig/jobs/installer/custom_dev/code.py +29 -20
  7. machineconfig/jobs/installer/custom_dev/espanso.py +64 -41
  8. machineconfig/jobs/installer/custom_dev/goes.py +41 -36
  9. machineconfig/jobs/installer/custom_dev/lvim.py +49 -33
  10. machineconfig/jobs/installer/custom_dev/nerdfont.py +71 -47
  11. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +25 -10
  12. machineconfig/jobs/installer/custom_dev/redis.py +51 -33
  13. machineconfig/jobs/installer/installer_data.json +17 -0
  14. machineconfig/jobs/installer/package_groups.py +271 -102
  15. machineconfig/jobs/python/python_cargo_build_share.py +0 -1
  16. machineconfig/jobs/python/python_ve_symlink.py +23 -15
  17. machineconfig/jobs/python/vscode/api.py +16 -8
  18. machineconfig/jobs/python/vscode/sync_code.py +42 -27
  19. machineconfig/scripts/python/cloud_repo_sync.py +8 -4
  20. machineconfig/scripts/python/devops_devapps_install.py +34 -26
  21. machineconfig/scripts/python/fire_jobs_args_helper.py +9 -0
  22. machineconfig/scripts/python/helpers/helpers4.py +2 -68
  23. machineconfig/scripts/python/helpers/repo_sync_helpers.py +4 -2
  24. machineconfig/scripts/python/interactive.py +24 -23
  25. machineconfig/scripts/python/mount_nfs.py +3 -6
  26. machineconfig/scripts/python/mount_ssh.py +3 -4
  27. machineconfig/scripts/python/sessions.py +10 -9
  28. machineconfig/scripts/python/start_slidev.py +14 -4
  29. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +36 -22
  30. machineconfig/utils/code.py +11 -2
  31. machineconfig/utils/installer.py +15 -9
  32. machineconfig/utils/path_extended.py +49 -7
  33. machineconfig/utils/ssh.py +22 -12
  34. machineconfig/utils/terminal.py +66 -92
  35. {machineconfig-4.94.dist-info → machineconfig-4.97.dist-info}/METADATA +1 -1
  36. {machineconfig-4.94.dist-info → machineconfig-4.97.dist-info}/RECORD +39 -38
  37. {machineconfig-4.94.dist-info → machineconfig-4.97.dist-info}/WHEEL +0 -0
  38. {machineconfig-4.94.dist-info → machineconfig-4.97.dist-info}/entry_points.txt +0 -0
  39. {machineconfig-4.94.dist-info → machineconfig-4.97.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from rich.console import Console
6
6
  from rich.panel import Panel
7
7
  from rich.table import Table
8
8
  from typing import Optional, cast, get_args
9
- from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS
9
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
10
10
 
11
11
  console = Console()
12
12
 
@@ -63,7 +63,7 @@ def main(
63
63
  if which is not None:
64
64
  return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
65
65
  if group is not None:
66
- return get_programs_by_category(package_group=group)
66
+ return install_group(package_group=group)
67
67
  if interactive:
68
68
  return install_interactively()
69
69
  typer.echo("❌ You must provide either --which, --group, or --interactive/-ia option.")
@@ -78,21 +78,32 @@ def install_interactively():
78
78
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
79
79
  from machineconfig.utils.installer import get_installers
80
80
  from machineconfig.utils.installer_utils.installer_class import Installer
81
- installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["ESSENTIAL", "DEV"])
81
+ installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
82
82
  installer_options = []
83
83
  for x in installers:
84
84
  installer_options.append(Installer(installer_data=x).get_description())
85
- category_options = [f"📦 {cat}" for cat in get_args(PACKAGE_GROUPS)]
86
- options = category_options + ["─" * 50] + installer_options
85
+
86
+ # Build category options and maintain a mapping from display text to actual category name
87
+ category_display_to_name: dict[str, str] = {}
88
+ for group_name, group_values in PACKAGE_GROUP2NAMES.items():
89
+ display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
90
+ category_display_to_name[display] = group_name
91
+
92
+ options_system = get_installers_system_groups()
93
+ for item in options_system:
94
+ display = f"📦 {item['appName']:<20} -- {item['doc']:<60}"
95
+ category_display_to_name[display] = item['appName']
96
+
97
+ options = list(category_display_to_name.keys()) + ["─" * 50] + installer_options
87
98
  program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", default="📦 essentials", fzf=True)
88
99
  installation_messages: list[str] = []
89
100
  for _an_idx, a_program_name in enumerate(program_names):
90
101
  if a_program_name.startswith("─"): # 50 dashes separator
91
102
  continue
92
103
  if a_program_name.startswith("📦 "):
93
- category_name = a_program_name[2:] # Remove "📦 " prefix
94
- if category_name in get_args(PACKAGE_GROUPS):
95
- get_programs_by_category(package_group=cast(PACKAGE_GROUPS, category_name))
104
+ category_name = category_display_to_name.get(a_program_name)
105
+ if category_name:
106
+ install_group(package_group=cast(PACKAGE_GROUPS, category_name))
96
107
  else:
97
108
  installer_idx = installer_options.index(a_program_name)
98
109
  an_installer_data = installers[installer_idx]
@@ -103,27 +114,24 @@ def install_interactively():
103
114
  console.print(panel)
104
115
 
105
116
 
106
- def get_programs_by_category(package_group: PACKAGE_GROUPS):
117
+ def install_group(package_group: PACKAGE_GROUPS):
107
118
  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))
108
119
  console.print(panel)
109
- from machineconfig.utils.installer import get_installers, install_all
120
+ from machineconfig.utils.installer import get_installers, install_bulk
110
121
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
111
- match package_group:
112
- case "ESSENTIAL":
113
- installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["ESSENTIAL"])
114
- install_all(installers_data=installers_)
115
- case "DEV":
116
- installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["DEV", "ESSENTIAL"])
117
- install_all(installers_data=installers_)
118
- case "DEV_SYSTEM" | "ESSENTIAL_SYSTEM":
119
- options_system = get_installers_system_groups()
120
- from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
121
- from machineconfig.utils.code import run_script
122
- for an_item in options_system:
123
- if an_item["appName"] == package_group:
124
- program = an_item["fileNamePattern"][get_normalized_arch()][get_os_name()]
125
- if program is not None:
126
- run_script(program)
122
+ if package_group in PACKAGE_GROUP2NAMES:
123
+ installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
124
+ install_bulk(installers_data=installers_)
125
+ else:
126
+ options_system = get_installers_system_groups()
127
+ from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
128
+ from machineconfig.utils.code import run_script
129
+ for an_item in options_system:
130
+ if an_item["appName"] == package_group:
131
+ program = an_item["fileNamePattern"][get_normalized_arch()][get_os_name()]
132
+ if program is not None:
133
+ run_script(program)
134
+ break
127
135
 
128
136
 
129
137
  def choose_from_system_package_groups(options_system: dict[str, tuple[str, str]]) -> str:
@@ -44,6 +44,10 @@ def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
44
44
 
45
45
  # Look for Fire-style arguments in sys.argv
46
46
  for arg in sys.argv:
47
+ # Skip the -- separator
48
+ if arg == '--':
49
+ continue
50
+
47
51
  # Match patterns like --key=value or --key value (but we'll focus on --key=value)
48
52
  if arg.startswith('--') and '=' in arg:
49
53
  key, value = arg[2:].split('=', 1) # Remove -- prefix and split on first =
@@ -53,6 +57,11 @@ def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
53
57
  elif arg.startswith('--') and '=' not in arg:
54
58
  # Handle boolean flags like --debug
55
59
  key = arg[2:] # Remove -- prefix
60
+
61
+ # Skip empty key (this would happen if someone just used '--')
62
+ if not key:
63
+ continue
64
+
56
65
  # Check if next argument exists and doesn't start with --
57
66
  arg_index = sys.argv.index(arg)
58
67
  if arg_index + 1 < len(sys.argv) and not sys.argv[arg_index + 1].startswith('--'):
@@ -1,7 +1,6 @@
1
- from typing import Any, Callable, Optional
2
- import inspect
3
- import os
4
1
 
2
+ from typing import Optional
3
+ import os
5
4
  from machineconfig.utils.path_extended import PathExtended as PathExtended
6
5
 
7
6
 
@@ -21,19 +20,6 @@ def search_for_files_of_interest(path_obj: PathExtended):
21
20
  return files
22
21
 
23
22
 
24
- def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
25
- # https://google.github.io/python-fire/guide/
26
- # https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing
27
- if not kwargs: # empty dict
28
- kwargs_str = ""
29
- else:
30
- # For fire module, all keyword arguments should be passed as --key value pairs
31
- tmp_list: list[str] = []
32
- for k, v in kwargs.items():
33
- tmp_list.append(f"--{k} {v}")
34
- kwargs_str = " " + " ".join(tmp_list) + " "
35
- return kwargs_str
36
-
37
23
 
38
24
  def parse_pyfile(file_path: str):
39
25
  print(f"🔍 Loading {file_path} ...")
@@ -84,58 +70,6 @@ def parse_pyfile(file_path: str):
84
70
  return options, func_args
85
71
 
86
72
 
87
- def interactively_run_function(func: Callable[[Any], Any]):
88
- sig = inspect.signature(func)
89
- params = list(sig.parameters.values())
90
- args = []
91
- kwargs = {}
92
- for param in params:
93
- if param.annotation is not inspect.Parameter.empty:
94
- hint = f" ({param.annotation.__name__})"
95
- else:
96
- hint = ""
97
- if param.default is not inspect.Parameter.empty:
98
- default = param.default
99
- value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) (default = {default}) : ")
100
- if value == "":
101
- value = default
102
- else:
103
- value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
104
- try:
105
- if param.annotation is not inspect.Parameter.empty:
106
- value = param.annotation
107
- except (TypeError, ValueError) as err:
108
- raise ValueError(f"Invalid input: {value} is not of type {param.annotation}") from err
109
- if param.kind == inspect.Parameter.KEYWORD_ONLY:
110
- kwargs[param.name] = value
111
- else:
112
- args.append((param.name, value))
113
- args_to_kwargs = dict(args)
114
- return args_to_kwargs, kwargs
115
-
116
-
117
- def get_attrs_recursively(obj: Any):
118
- if hasattr(obj, "__dict__"):
119
- res = {}
120
- for k, v in obj.__dict__.items():
121
- res[k] = get_attrs_recursively(v)
122
- return res
123
- return obj
124
-
125
-
126
- # def run_on_remote(func_file: str, args: argparse.Namespace):
127
- # host = choose_ssh_host(multi=False)
128
- # assert isinstance(host, str), f"host must be a string. Got {type(host)}"
129
- # from machineconfig.cluster.remote_machine import RemoteMachine, RemoteMachineConfig
130
- # config = RemoteMachineConfig(copy_repo=True, update_repo=False, update_essential_repos=True,
131
- # notify_upon_completion=True, ssh_params=dict(host=host),
132
- # # to_email=None, email_config_name='enaut',
133
- # data=[],
134
- # ipython=False, interactive=args.interactive, pdb=False, pudb=args.debug, wrap_in_try_except=False,
135
- # transfer_method="sftp")
136
- # m = RemoteMachine(func=func_file, func_kwargs=None, config=config)
137
- # m.run()
138
-
139
73
 
140
74
  def find_repo_root_path(start_path: str) -> Optional[str]:
141
75
  root_files = ["setup.py", "pyproject.toml", ".git"]
@@ -1,10 +1,11 @@
1
1
  from machineconfig.utils.path_extended import PathExtended as PathExtended
2
- from machineconfig.utils.terminal import Terminal
2
+ from machineconfig.utils.terminal import Response
3
3
  from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
4
4
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
5
5
  from machineconfig.utils.io import read_ini
6
6
  from machineconfig.utils.code import write_shell_script_to_file
7
7
  import platform
8
+ import subprocess
8
9
  from rich.console import Console
9
10
  from rich.panel import Panel
10
11
 
@@ -89,7 +90,8 @@ sudo chmod 700 $HOME/.ssh
89
90
  sudo chmod +x $HOME/dotfiles/scripts/linux -R
90
91
  """
91
92
  shell_path = write_shell_script_to_file(shell_script=script)
92
- Terminal().run(f". {shell_path}", shell="bash").capture().print()
93
+ completed = subprocess.run(f". {shell_path}", capture_output=True, check=False, text=True, shell=True)
94
+ Response.from_completed_process(completed).capture().print()
93
95
 
94
96
  console.print(Panel("✅ Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
95
97
 
@@ -17,7 +17,6 @@ for better user experience with checkbox selections.
17
17
 
18
18
  """
19
19
 
20
- import subprocess
21
20
  import sys
22
21
  from pathlib import Path
23
22
  from platform import system
@@ -28,21 +27,12 @@ from questionary import Choice
28
27
  from rich.console import Console
29
28
  from rich.panel import Panel
30
29
  from rich.text import Text
31
-
30
+ from machineconfig.utils.code import run_script as run_command
32
31
 
33
32
  _ = cast
34
33
  console = Console()
35
34
 
36
35
 
37
- def run_command(command: str, description: str) -> bool:
38
- """Execute a shell command and return success status."""
39
- console.print(f"\n🔧 {description}", style="bold cyan")
40
- try:
41
- result = subprocess.run(command, shell=True, check=True, capture_output=False)
42
- return result.returncode == 0
43
- except subprocess.CalledProcessError as e:
44
- console.print(f"❌ Error executing command: {e}", style="bold red")
45
- return False
46
36
  def display_header() -> None:
47
37
  """Display the script header."""
48
38
  header_text = Text("MACHINE CONFIGURATION", style="bold magenta")
@@ -111,6 +101,7 @@ def get_installation_choices() -> list[str]:
111
101
  Choice(value="TerminalEyeCandy", title="🎨 Install ASCII Art Libraries - Terminal visualization tools", checked=False),
112
102
  Choice(value="install_repos", title="🐍 Install Repos - Set up Python environment and repositories permanently.", checked=False),
113
103
  Choice(value="install_ssh_server", title="🔒 Install SSH Server - Set up remote access", checked=False),
104
+ Choice(value="install_shell_profile", title="🐚 Configure Shell Profile - Source machineconfig shell initialization", checked=False),
114
105
  Choice(value="create_symlinks", title="🔗 Create Symlinks - Set up configuration symlinks (finish dotfiles transfer first)", checked=False),
115
106
  Choice(value="retrieve_repositories", title="📚 Retrieve Repositories - Clone repositories to ~/code", checked=False),
116
107
  Choice(value="retrieve_data", title="💾 Retrieve Data - Backup restoration", checked=False),
@@ -124,36 +115,35 @@ def get_installation_choices() -> list[str]:
124
115
 
125
116
  def execute_installations(selected_options: list[str]) -> None:
126
117
  """Execute the selected installation options."""
127
- # Always start with VE setup
128
- console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Setting up base virtual environment[/italic]", border_style="green"))
129
- run_command("curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/ve.sh | bash", "Setting up base virtual environment")
130
-
118
+ if system() == "Windows":
119
+ run_command("$HOME/.local/bin/uv.exe self update")
120
+ else:
121
+ run_command("$HOME/.local/bin/uv self update")
131
122
  for maybe_a_group in selected_options:
132
123
  if maybe_a_group in ("ESSENTIAL", "DEV", "ESSENTIAL_SYSTEM", "DEV_SYSTEM", "TerminalEyeCandy"):
133
124
  console.print(Panel("⚡ [bold bright_yellow]CLI APPLICATIONS[/bold bright_yellow]\n[italic]Command-line tools installation[/italic]", border_style="bright_yellow"))
134
125
  console.print("🔧 Installing CLI applications", style="bold cyan")
135
126
  try:
136
127
  from machineconfig.scripts.python.devops_devapps_install import main as devops_devapps_install_main
137
- # maybe_a_group = cast(PA, maybe_a_group)
138
128
  devops_devapps_install_main(group=maybe_a_group) # type: ignore
139
129
  console.print("✅ CLI applications installed successfully", style="bold green")
140
130
  except Exception as e:
141
131
  console.print(f"❌ Error installing CLI applications: {e}", style="bold red")
142
- run_command(". $HOME/.bashrc", "Reloading bash configuration")
132
+ run_command(". $HOME/.bashrc")
143
133
 
144
134
  if "upgrade_system" in selected_options:
145
135
  if system() == "Windows":
146
136
  console.print("❌ System upgrade is not applicable on Windows via this script.", style="bold red")
147
137
  elif system() == "Linux":
148
138
  console.print(Panel("🔄 [bold magenta]SYSTEM UPDATE[/bold magenta]\n[italic]Package management[/italic]", border_style="magenta"))
149
- run_command("sudo nala upgrade -y", "Upgrading system packages")
139
+ run_command("sudo nala upgrade -y")
150
140
  else:
151
141
  console.print(f"❌ System upgrade not supported on {system()}.", style="bold red")
152
142
  if "install_repos" in selected_options:
153
143
  console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Virtual environment setup[/italic]", border_style="green"))
154
144
  from machineconfig import setup_linux as module
155
145
  script = Path(module.__file__).parent / "repos.sh"
156
- run_command(f"bash {script}", "Setting up Python environment and repositories")
146
+ run_command(f"bash {script}")
157
147
 
158
148
  if "install_ssh_server" in selected_options:
159
149
  console.print(Panel("🔒 [bold red]SSH SERVER[/bold red]\n[italic]Remote access setup[/italic]", border_style="red"))
@@ -162,9 +152,20 @@ def execute_installations(selected_options: list[str]) -> None:
162
152
  Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
163
153
  Start-Service sshd
164
154
  Set-Service -Name sshd -StartupType 'Automatic'"""
165
- run_command(f'powershell -Command "{powershell_script}"', "Installing and configuring SSH server")
155
+ run_command(f'powershell -Command "{powershell_script}"')
166
156
  else:
167
- run_command("sudo nala install openssh-server -y", "Installing SSH server")
157
+ run_command("sudo nala install openssh-server -y")
158
+
159
+ if "install_shell_profile" in selected_options:
160
+ console.print(Panel("🐚 [bold green]SHELL PROFILE[/bold green]\n[italic]Shell configuration setup[/italic]", border_style="green"))
161
+ console.print("🔧 Configuring shell profile", style="bold cyan")
162
+ try:
163
+ from machineconfig.profile.create import main_profile
164
+
165
+ main_profile()
166
+ console.print("✅ Shell profile configured successfully", style="bold green")
167
+ except Exception as e:
168
+ console.print(f"❌ Error configuring shell profile: {e}", style="bold red")
168
169
 
169
170
  if "create_symlinks" in selected_options:
170
171
  display_dotfiles_instructions()
@@ -178,8 +179,8 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
178
179
  console.print("✅ Symlinks created successfully", style="bold green")
179
180
  except Exception as e:
180
181
  console.print(f"❌ Error creating symlinks: {e}", style="bold red")
181
- run_command("sudo chmod 600 $HOME/.ssh/*", "Setting SSH key permissions")
182
- run_command("sudo chmod 700 $HOME/.ssh", "Setting SSH directory permissions")
182
+ run_command("sudo chmod 600 $HOME/.ssh/*")
183
+ run_command("sudo chmod 700 $HOME/.ssh")
183
184
  else:
184
185
  console.print("⏭️ Skipping symlink creation - finish dotfiles transfer first", style="yellow")
185
186
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  from machineconfig.utils.path_extended import PathExtended as PathExtended
4
4
  from machineconfig.utils.ssh import SSH
5
- from machineconfig.utils.terminal import Terminal
6
5
  from machineconfig.utils.options import choose_from_options, choose_ssh_host
7
6
 
8
7
  import platform
8
+ import subprocess
9
9
 
10
10
 
11
11
  def main():
@@ -49,15 +49,14 @@ share_path={share_path}
49
49
  local_mount_point={local_mount_point}
50
50
  """
51
51
  # PROGRAM_PATH.write_text(txt)
52
- import subprocess
53
-
54
52
  subprocess.run(txt, shell=True, check=True)
55
53
 
56
54
  print("✅ Mount paths prepared successfully!\n")
57
55
 
58
56
  elif platform.system() == "Windows":
59
57
  print("\n🔍 Checking existing drives...")
60
- print(Terminal().run("Get-PSDrive -PSProvider 'FileSystem'", shell="powershell").op)
58
+ completed = subprocess.run(["powershell", "-Command", "Get-PSDrive -PSProvider 'FileSystem'"], capture_output=True, check=False, text=True)
59
+ print((completed.stdout or "").strip())
61
60
  driver_letter = input(r"🖥️ Choose driver letter (e.g., Z:\\) [Avoid already used ones]: ") or "Z:\\"
62
61
  txt = f"""
63
62
  $server = "{remote_server}"
@@ -65,8 +64,6 @@ $sharePath = "{share_path}"
65
64
  $driveLetter = "{driver_letter}"
66
65
  """
67
66
  # PROGRAM_PATH.write_text(txt)
68
- import subprocess
69
-
70
67
  subprocess.run(txt, shell=True, check=True)
71
68
  print("✅ Drive letter selected and configuration saved!\n")
72
69
 
@@ -1,8 +1,8 @@
1
1
  """Mount a remote SSHFS share on a local directory"""
2
2
 
3
3
  from platform import system
4
+ import subprocess
4
5
  from machineconfig.utils.ssh import SSH
5
- from machineconfig.utils.terminal import Terminal
6
6
  from machineconfig.utils.path_extended import PathExtended as PathExtended
7
7
 
8
8
  from machineconfig.utils.options import choose_ssh_host
@@ -27,7 +27,8 @@ def main():
27
27
 
28
28
  if system() == "Windows":
29
29
  print("\n🔍 Checking existing drives...")
30
- print(Terminal().run("net use", shell="powershell").op)
30
+ completed = subprocess.run(["powershell", "-Command", "net use"], capture_output=True, check=False, text=True)
31
+ print((completed.stdout or "").strip())
31
32
  driver_letter = input(r"🖥️ Choose driver letter (e.g., Z:\\) [Avoid already used ones]: ") or "Z:\\"
32
33
  else:
33
34
  driver_letter = None
@@ -53,8 +54,6 @@ fusermount -u /mnt/dbhdd
53
54
  raise ValueError(f"❌ Not implemented for this system: {system()}")
54
55
 
55
56
  # PROGRAM_PATH.write_text(txt, encoding="utf-8")
56
- import subprocess
57
-
58
57
  subprocess.run(txt, shell=True, check=True)
59
58
  print("✅ Configuration saved successfully!\n")
60
59
 
@@ -50,7 +50,7 @@ def select_layout(layouts_json_file: Path, selected_layouts_names: Optional[list
50
50
  return layouts_chosen
51
51
 
52
52
 
53
- def handle_layout_args(layout_path: str, select_layouts: Optional[str], select_interactively: bool) -> list["LayoutConfig"]:
53
+ def find_layout_file(layout_path: str, ) -> Path:
54
54
  from machineconfig.utils.path_extended import PathExtended
55
55
  from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
56
56
  from machineconfig.utils.options import choose_from_options
@@ -66,23 +66,24 @@ def handle_layout_args(layout_path: str, select_layouts: Optional[str], select_i
66
66
  choice_file = PathExtended(choice_file)
67
67
  else:
68
68
  choice_file = path_obj
69
- selected_layouts = select_layout(layouts_json_file=choice_file, selected_layouts_names=select_layouts.split(",") if select_layouts else None, select_interactively=select_interactively)
70
- return selected_layouts
69
+ return choice_file
71
70
 
72
71
 
73
72
  def launch(layout_path: str = typer.Argument(..., help="Path to the layout.json file"),
74
73
  max_tabs: int = typer.Option(10, help="A Sanity checker that throws an error if any layout exceeds the maximum number of tabs to launch."),
75
74
  max_layouts: int = typer.Option(10, help="A Sanity checker that throws an error if the total number of layouts exceeds this number."),
76
75
  sleep_inbetween: float = typer.Option(1.0, help="Sleep time in seconds between launching layouts"),
77
- monitor: bool = typer.Option(False, help="Monitor the layout sessions for completion"),
78
- parallel: bool = typer.Option(False, help="Launch multiple layouts in parallel"),
79
- kill_upon_completion: bool = typer.Option(False, help="Kill session(s) upon completion (only relevant if monitor flag is set)"),
80
- select: Optional[str] = typer.Option(None, help="Comma separated names of layouts to be selected from the layout file passed"),
81
- select_interactively: bool = typer.Option(False, help="Select layouts interactively")
76
+ monitor: bool = typer.Option(False, "--monitor", "-m", help="Monitor the layout sessions for completion"),
77
+ parallel: bool = typer.Option(False, "--parallel", "-p", help="Launch multiple layouts in parallel"),
78
+ kill_upon_completion: bool = typer.Option(False, "--kill-upon-completion", "-k", help="Kill session(s) upon completion (only relevant if monitor flag is set)"),
79
+ choose: Optional[str] = typer.Option(None, "--choose", "-c", help="Comma separated names of layouts to be selected from the layout file passed"),
80
+ choose_interactively: bool = typer.Option(False, "--choose-interactively", "-ia", help="Select layouts interactively")
82
81
  ):
83
82
  """
83
+ Launch terminal sessions based on a layout configuration file.
84
84
  """
85
- layouts_selected = handle_layout_args(layout_path=layout_path, select_layouts=select, select_interactively=select_interactively)
85
+ layout_path_resolved = find_layout_file(layout_path=layout_path)
86
+ layouts_selected = select_layout(layouts_json_file=layout_path_resolved, selected_layouts_names=choose.split(",") if choose else None, select_interactively=choose_interactively)
86
87
 
87
88
  # ============= Basic sanity checks =============
88
89
  if len(layouts_selected) > max_layouts:
@@ -5,7 +5,7 @@ slidev
5
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_extended import PathExtended as PathExtended
8
- from machineconfig.utils.terminal import Terminal
8
+ from machineconfig.utils.terminal import Response
9
9
  from typing import Annotated, Optional
10
10
  import typer
11
11
  import subprocess
@@ -16,10 +16,20 @@ PORT_DEFAULT = 3030
16
16
  SLIDEV_REPO = PathExtended(CONFIG_PATH).joinpath(".cache/slidev")
17
17
  if not SLIDEV_REPO.joinpath("components").exists():
18
18
  print("📦 Initializing Slidev repository...")
19
- Terminal(stderr=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE).run(f"cd {SLIDEV_REPO.parent};npm init slidev@latest")
19
+ subprocess.run(f"cd {SLIDEV_REPO.parent};npm init slidev@latest", check=False, shell=True, text=True)
20
20
  print("✅ Slidev repository initialized successfully!\n")
21
21
 
22
22
 
23
+ def _execute_with_shell(command: str) -> Response:
24
+ if platform.system() == "Windows":
25
+ completed = subprocess.run(["powershell", "-Command", command], capture_output=True, check=False, text=True)
26
+ else:
27
+ completed = subprocess.run(command, capture_output=True, check=False, text=True, shell=True)
28
+ response = Response.from_completed_process(completed)
29
+ response.print()
30
+ return response
31
+
32
+
23
33
  def jupyter_to_markdown(file: PathExtended):
24
34
  op_dir = file.parent.joinpath("presentation")
25
35
  print("📝 Converting Jupyter notebook to markdown...")
@@ -35,9 +45,9 @@ def jupyter_to_markdown(file: PathExtended):
35
45
  # for key, value in resources['outputs'].items():
36
46
 
37
47
  cmd = f"jupyter nbconvert --to markdown --no-prompt --no-input --output-dir {op_dir} --output slides_raw.md {file}"
38
- Terminal().run(cmd, shell="powershell").print()
48
+ _execute_with_shell(cmd)
39
49
  cmd = f"jupyter nbconvert --to html --no-prompt --no-input --output-dir {op_dir} {file}"
40
- Terminal().run(cmd, shell="powershell").print()
50
+ _execute_with_shell(cmd)
41
51
 
42
52
  op_file = op_dir.joinpath("slides_raw.md")
43
53
  slide_separator = "\n\n---\n\n"
@@ -8,6 +8,7 @@ import platform
8
8
  # from uuid import uuid4
9
9
  import os
10
10
  from typing import Any
11
+ from rich import box
11
12
  from rich.console import Console
12
13
  from rich.panel import Panel
13
14
 
@@ -28,34 +29,41 @@ system = platform.system() # Linux or Windows
28
29
  assert system == "Windows", "This script is only for Windows."
29
30
 
30
31
 
32
+ def render_banner(message: str, title: str, border_style: str, box_style: box.Box) -> None:
33
+ console.print(Panel.fit(message, title=title, border_style=border_style, box=box_style, padding=(1, 4)))
34
+
35
+
31
36
  class TerminalSettings(object):
32
37
  def __init__(self):
33
38
  # Grabbing Terminal Settings file:
34
- print(f"\n{'=' * 80}\n🔍 INITIALIZING TERMINAL SETTINGS 🔍\n{'=' * 80}")
39
+ console.print()
40
+ render_banner("🔍 INITIALIZING TERMINAL SETTINGS 🔍", "Windows Terminal", "cyan", box.DOUBLE)
41
+ console.print()
35
42
  tmp = os.getenv("LOCALAPPDATA")
36
43
  if not isinstance(tmp, str):
37
- print("❌ ERROR: Could not find LOCALAPPDATA environment variable!")
44
+ console.print("❌ ERROR: Could not find LOCALAPPDATA environment variable!")
38
45
  raise ValueError("Could not find LOCALAPPDATA environment variable.")
39
46
  self.path = PathExtended(tmp).joinpath(r"Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json")
40
47
  backup_name = f".orig_{randstr()}"
41
- print(f"📝 Creating backup of original settings as {backup_name}...")
48
+ console.print(f"📝 Creating backup of original settings as {backup_name}...")
42
49
  self.path.copy(append=backup_name)
43
- print(f"📂 Loading Windows Terminal settings from: {self.path}")
50
+ console.print(f"📂 Loading Windows Terminal settings from: {self.path}")
44
51
  self.dat: dict[str, Any] = read_json(self.path)
45
52
  # Use a plain Python list for profiles
46
53
  self.profs = list(self.dat["profiles"]["list"])
47
- console = Console()
48
- console.print(Panel(f"✅ Successfully loaded {len(self.profs)} profiles", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue"))
54
+ console.print(Panel(f"✅ Successfully loaded {len(self.profs)} profiles", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue", box=box.ROUNDED))
49
55
 
50
56
  def save_terminal_settings(self):
51
- print(f"\n💾 Saving terminal settings to: {self.path}")
57
+ console.print()
58
+ console.print(f"💾 Saving terminal settings to: {self.path}")
52
59
  self.dat["profiles"]["list"] = list(self.profs)
53
60
  save_json(obj=self.dat, path=self.path, indent=5)
54
- console.print(Panel("✅ Settings saved successfully!", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue"))
61
+ console.print(Panel("✅ Settings saved successfully!", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue", box=box.ROUNDED))
55
62
 
56
63
  # ========================= Terminal Settings =========================================
57
64
  def update_default_settings(self):
58
- print("\n⚙️ Updating default terminal settings...")
65
+ console.print()
66
+ console.print("⚙️ Updating default terminal settings...")
59
67
  # Changing start up settings:
60
68
  self.dat["startOnUserLogin"] = True
61
69
  self.dat["launchMode"] = "fullscreen"
@@ -64,12 +72,13 @@ class TerminalSettings(object):
64
72
  self.dat["copyOnSelect"] = True
65
73
  self.dat["profiles"]["defaults"]["padding"] = "0"
66
74
  self.dat["profiles"]["defaults"]["useAcrylic"] = False
67
- console.print(Panel("✅ Default settings updated", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue"))
75
+ console.print(Panel("✅ Default settings updated", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue", box=box.ROUNDED))
68
76
 
69
77
  # 1- Customizing Powershell========================================================
70
78
  # as opposed to Windows Powershell
71
79
  def customize_powershell(self, nerd_font: bool = True):
72
- print("\n🛠️ Customizing PowerShell profile...")
80
+ console.print()
81
+ console.print("🛠️ Customizing PowerShell profile...")
73
82
  pwsh: dict[str, Any] = dict(
74
83
  name="PowerShell",
75
84
  commandline="pwsh",
@@ -79,45 +88,50 @@ class TerminalSettings(object):
79
88
  startingDirectory="%USERPROFILE%", # "%USERPROFILE%", # None: inherent from parent process.
80
89
  )
81
90
  if nerd_font:
82
- print("🔤 Setting PowerShell font to CaskaydiaCove Nerd Font...")
91
+ console.print("🔤 Setting PowerShell font to CaskaydiaCove Nerd Font...")
83
92
  pwsh["font"] = dict(face="CaskaydiaCove Nerd Font") # because oh-my-posh uses glyphs from this font.
84
93
 
85
94
  for idx, item in enumerate(self.profs):
86
95
  if item["name"] == "PowerShell":
87
96
  self.profs[idx].update(pwsh)
88
- console.print(Panel("✅ PowerShell profile customized successfully", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue"))
97
+ console.print(Panel("✅ PowerShell profile customized successfully", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue", box=box.ROUNDED))
89
98
  break
90
99
  else:
91
- console.print(Panel("❌ Couldn't customize PowerShell because profile not found, try to install it first.", title="[bold red]Terminal Settings[/bold red]", border_style="red"))
100
+ console.print(Panel("❌ Couldn't customize PowerShell because profile not found, try to install it first.", title="[bold red]Terminal Settings[/bold red]", border_style="red", box=box.ROUNDED))
92
101
 
93
102
  def make_powershell_default_profile(self):
94
- print("\n🌟 Setting PowerShell as the default profile...")
103
+ console.print()
104
+ console.print("🌟 Setting PowerShell as the default profile...")
95
105
  for profile in self.profs:
96
106
  if profile["name"] == "PowerShell":
97
107
  self.dat["defaultProfile"] = profile["guid"]
98
- console.print(Panel("✅ PowerShell is now the default profile!", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue"))
108
+ console.print(Panel("✅ PowerShell is now the default profile!", title="[bold blue]Terminal Settings[/bold blue]", border_style="blue", box=box.ROUNDED))
99
109
  break
100
110
  else:
101
- console.print(Panel("❌ PowerShell profile was not found in the list of profiles and therefore was not made the default.", title="[bold red]Terminal Settings[/bold red]", border_style="red"))
111
+ console.print(Panel("❌ PowerShell profile was not found in the list of profiles and therefore was not made the default.", title="[bold red]Terminal Settings[/bold red]", border_style="red", box=box.ROUNDED))
102
112
 
103
113
 
104
114
  def main():
105
- print(f"\n{'=' * 80}\n🖥️ WINDOWS TERMINAL SETUP 🖥️\n{'=' * 80}")
115
+ console.print()
116
+ render_banner("🖥️ WINDOWS TERMINAL SETUP 🖥️", "Windows Terminal", "cyan", box.DOUBLE)
117
+ console.print()
106
118
  shell = {"powershell": "pwsh.exe", "Windows Powershell": "powershell.exe"}["powershell"].split(".exe", maxsplit=1)[0]
107
119
  if shell == "pwsh":
108
- print("🚀 Starting Windows Terminal configuration with PowerShell...")
120
+ console.print("🚀 Starting Windows Terminal configuration with PowerShell...")
109
121
  ts = TerminalSettings()
110
122
  ts.update_default_settings()
111
123
  ts.customize_powershell(nerd_font=True)
112
124
  ts.make_powershell_default_profile()
113
- print("⌨️ Adding keyboard shortcut for pane zoom (ctrl+shift+z)...")
125
+ console.print("⌨️ Adding keyboard shortcut for pane zoom (ctrl+shift+z)...")
114
126
  ts.dat["actions"].append({"command": "togglePaneZoom", "keys": "ctrl+shift+z"})
115
127
 
116
128
  ts.save_terminal_settings()
117
- print(f"\n{'=' * 80}\n✨ WINDOWS TERMINAL SETUP COMPLETE ✨\n{'=' * 80}")
129
+ console.print()
130
+ render_banner("✨ WINDOWS TERMINAL SETUP COMPLETE ✨", "Windows Terminal", "green", box.DOUBLE)
131
+ console.print()
118
132
  else:
119
133
  error_msg = "❌ ERROR: Only PowerShell is supported, not Windows PowerShell!"
120
- print(error_msg)
134
+ console.print(error_msg)
121
135
  raise NotImplementedError(error_msg)
122
136
 
123
137