machineconfig 7.53__py3-none-any.whl → 7.69__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.
- machineconfig/cluster/sessions_managers/utils/maker.py +21 -9
- machineconfig/jobs/installer/custom/boxes.py +2 -2
- machineconfig/jobs/installer/custom/hx.py +15 -12
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -1
- machineconfig/jobs/installer/custom_dev/sysabc.py +39 -34
- machineconfig/jobs/installer/custom_dev/wezterm.py +0 -4
- machineconfig/jobs/installer/installer_data.json +103 -35
- machineconfig/jobs/installer/package_groups.py +28 -13
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/linux/wrap_mcfg +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +3 -0
- machineconfig/scripts/python/croshell.py +22 -17
- machineconfig/scripts/python/devops.py +3 -4
- machineconfig/scripts/python/devops_navigator.py +0 -4
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +17 -15
- machineconfig/scripts/python/ftpx.py +13 -11
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers_cloud/cloud_copy.py +28 -21
- machineconfig/scripts/python/helpers_cloud/cloud_helpers.py +1 -1
- machineconfig/scripts/python/helpers_cloud/cloud_sync.py +8 -7
- machineconfig/scripts/python/helpers_croshell/crosh.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
- machineconfig/scripts/python/helpers_devops/cli_self.py +7 -6
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_utils.py +2 -73
- machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
- machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -3
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
- machineconfig/scripts/python/helpers_navigator/command_tree.py +50 -18
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +13 -5
- machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
- machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
- machineconfig/scripts/python/helpers_repos/record.py +2 -1
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +5 -5
- machineconfig/scripts/python/helpers_utils/download.py +152 -0
- machineconfig/scripts/python/helpers_utils/path.py +4 -2
- machineconfig/scripts/python/interactive.py +11 -14
- machineconfig/scripts/python/{machineconfig.py → mcfg_entry.py} +4 -0
- machineconfig/scripts/python/msearch.py +21 -2
- machineconfig/scripts/python/nw/devops_add_ssh_key.py +21 -5
- machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
- machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
- machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
- machineconfig/scripts/python/sessions.py +35 -20
- machineconfig/scripts/python/terminal.py +2 -2
- machineconfig/scripts/python/utils.py +12 -10
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/shells/pwsh/init.ps1 +1 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
- machineconfig/settings/shells/zsh/init.sh +0 -7
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
- machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -11
- machineconfig/setup_windows/uv.ps1 +8 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -11
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +4 -2
- machineconfig/utils/accessories.py +7 -4
- machineconfig/utils/code.py +6 -4
- machineconfig/utils/files/headers.py +2 -2
- machineconfig/utils/installer_utils/install_from_url.py +180 -0
- machineconfig/utils/installer_utils/installer_class.py +56 -46
- machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +71 -65
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +1 -25
- machineconfig/utils/meta.py +28 -15
- machineconfig/utils/options.py +4 -4
- machineconfig/utils/path_extended.py +40 -19
- machineconfig/utils/path_helper.py +33 -31
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/ssh.py +330 -99
- machineconfig/utils/ve.py +11 -4
- machineconfig-7.69.dist-info/METADATA +124 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/RECORD +85 -83
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/entry_points.txt +2 -2
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
- machineconfig/scripts/python/explore.py +0 -49
- machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig-7.53.dist-info/METADATA +0 -94
- /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
- /machineconfig/scripts/{Restore-ThunderbirdProfile.ps1 → windows/mounts/Restore-ThunderbirdProfile.ps1} +0 -0
- /machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/WHEEL +0 -0
- {machineconfig-7.53.dist-info → machineconfig-7.69.dist-info}/top_level.txt +0 -0
|
@@ -1,55 +1,16 @@
|
|
|
1
1
|
"""Devops Devapps Install"""
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
|
-
from
|
|
5
|
-
from rich.panel import Panel
|
|
6
|
-
from rich.table import Table
|
|
7
|
-
from typing import Optional, Annotated
|
|
4
|
+
from typing import Annotated, Optional
|
|
8
5
|
from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
|
|
6
|
+
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
9
7
|
|
|
10
|
-
console = Console()
|
|
11
8
|
|
|
12
9
|
|
|
13
|
-
def
|
|
14
|
-
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
15
|
-
from difflib import get_close_matches
|
|
16
|
-
close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
17
|
-
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
18
|
-
if close_matches:
|
|
19
|
-
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
20
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
21
|
-
for i, match in enumerate(close_matches, 1):
|
|
22
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
|
|
23
|
-
console.print(table)
|
|
24
|
-
else:
|
|
25
|
-
console.print("📋 Here are some available options:", style="blue")
|
|
26
|
-
# Show first 10 installers as examples
|
|
27
|
-
if len(all_names) > 10:
|
|
28
|
-
sample_names = all_names[:10]
|
|
29
|
-
else:
|
|
30
|
-
sample_names = all_names
|
|
31
|
-
table = Table(show_header=False, box=None, pad_edge=False)
|
|
32
|
-
for i, name in enumerate(sample_names, 1):
|
|
33
|
-
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
|
|
34
|
-
console.print(table)
|
|
35
|
-
if len(all_names) > 10:
|
|
36
|
-
console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
|
|
37
|
-
|
|
38
|
-
panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
|
|
39
|
-
console.print(panel)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def main_with_parser():
|
|
43
|
-
import typer
|
|
44
|
-
app = typer.Typer()
|
|
45
|
-
app.command()(main)
|
|
46
|
-
app()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def main(
|
|
10
|
+
def main_installer_cli(
|
|
50
11
|
which: Annotated[Optional[str], typer.Argument(..., help="Comma-separated list of program/groups names to install (if --group flag is set).")] = None,
|
|
51
12
|
group: Annotated[bool, typer.Option(..., "--group", "-g", help="Treat 'which' as a group name. A group is bundle of apps.")] = False,
|
|
52
|
-
interactive: Annotated[bool, typer.Option(..., "--interactive", "-
|
|
13
|
+
interactive: Annotated[bool, typer.Option(..., "--interactive", "-i", help="Interactive selection of programs to install.")] = False,
|
|
53
14
|
) -> None:
|
|
54
15
|
if interactive:
|
|
55
16
|
return install_interactively()
|
|
@@ -61,6 +22,10 @@ def main(
|
|
|
61
22
|
return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
|
|
62
23
|
else:
|
|
63
24
|
if group:
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.table import Table
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
64
29
|
typer.echo("❌ You must provide a group name when using the --group/-g option.")
|
|
65
30
|
res = get_group_name_to_repr()
|
|
66
31
|
console.print("[bold blue]Here are the available groups:[/bold blue]")
|
|
@@ -98,20 +63,18 @@ def get_group_name_to_repr() -> dict[str, str]:
|
|
|
98
63
|
def install_interactively():
|
|
99
64
|
from machineconfig.utils.options import choose_from_options
|
|
100
65
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
101
|
-
from machineconfig.utils.
|
|
66
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
102
67
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
68
|
+
from rich.console import Console
|
|
69
|
+
from rich.panel import Panel
|
|
70
|
+
# from rich.table import Table
|
|
103
71
|
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
|
-
|
|
72
|
+
installer_options = [Installer(installer_data=x).get_description() for x in installers]
|
|
108
73
|
category_display_to_name = get_group_name_to_repr()
|
|
109
|
-
options = list(category_display_to_name.keys()) +
|
|
110
|
-
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY",
|
|
74
|
+
options = list(category_display_to_name.keys()) + installer_options
|
|
75
|
+
program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", fzf=True)
|
|
111
76
|
installation_messages: list[str] = []
|
|
112
77
|
for _an_idx, a_program_name in enumerate(program_names):
|
|
113
|
-
if a_program_name.startswith("─"): # 50 dashes separator
|
|
114
|
-
continue
|
|
115
78
|
if a_program_name.startswith("📦 "):
|
|
116
79
|
category_name = category_display_to_name.get(a_program_name)
|
|
117
80
|
if category_name:
|
|
@@ -122,59 +85,102 @@ def install_interactively():
|
|
|
122
85
|
status_message = Installer(an_installer_data).install_robust(version=None) # finish the task - this returns a status message, not a command
|
|
123
86
|
installation_messages.append(status_message)
|
|
124
87
|
if installation_messages:
|
|
88
|
+
console = Console()
|
|
89
|
+
|
|
125
90
|
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
91
|
console.print(panel)
|
|
127
92
|
|
|
128
93
|
|
|
129
94
|
def install_group(package_group: str):
|
|
130
|
-
from machineconfig.utils.
|
|
95
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers, install_bulk
|
|
131
96
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
97
|
+
from rich.console import Console
|
|
98
|
+
from rich.panel import Panel
|
|
99
|
+
# from rich.table import Table
|
|
132
100
|
if package_group in PACKAGE_GROUP2NAMES:
|
|
133
101
|
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))
|
|
102
|
+
console = Console()
|
|
134
103
|
console.print(panel)
|
|
135
104
|
installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
|
|
136
105
|
install_bulk(installers_data=installers_)
|
|
137
106
|
return
|
|
138
|
-
|
|
107
|
+
console = Console()
|
|
108
|
+
console.print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
|
|
109
|
+
def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
|
|
110
|
+
"""Handle installer not found with friendly suggestions using fuzzy matching."""
|
|
111
|
+
from difflib import get_close_matches
|
|
112
|
+
from rich.console import Console
|
|
113
|
+
from rich.panel import Panel
|
|
114
|
+
from rich.table import Table
|
|
115
|
+
close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
|
|
116
|
+
console = Console()
|
|
117
|
+
|
|
118
|
+
console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
|
|
119
|
+
if close_matches:
|
|
120
|
+
console.print("🤔 Did you mean one of these?", style="yellow")
|
|
121
|
+
table = Table(show_header=False, box=None, pad_edge=False)
|
|
122
|
+
for i, match in enumerate(close_matches, 1):
|
|
123
|
+
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
|
|
124
|
+
console.print(table)
|
|
125
|
+
else:
|
|
126
|
+
console.print("📋 Here are some available options:", style="blue")
|
|
127
|
+
# Show first 10 installers as examples
|
|
128
|
+
if len(all_names) > 10:
|
|
129
|
+
sample_names = all_names[:10]
|
|
130
|
+
else:
|
|
131
|
+
sample_names = all_names
|
|
132
|
+
table = Table(show_header=False, box=None, pad_edge=False)
|
|
133
|
+
for i, name in enumerate(sample_names, 1):
|
|
134
|
+
table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
|
|
135
|
+
console.print(table)
|
|
136
|
+
if len(all_names) > 10:
|
|
137
|
+
console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
|
|
138
|
+
|
|
139
|
+
panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
|
|
140
|
+
console.print(panel)
|
|
141
|
+
|
|
139
142
|
def install_clis(clis_names: list[str]):
|
|
140
143
|
from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
|
|
141
|
-
from machineconfig.utils.
|
|
144
|
+
from machineconfig.utils.installer_utils.installer_runner import get_installers
|
|
142
145
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
146
|
+
from rich.console import Console
|
|
147
|
+
all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
|
|
143
148
|
total_messages: list[str] = []
|
|
144
|
-
for
|
|
145
|
-
|
|
149
|
+
for a_cli_name in clis_names:
|
|
150
|
+
if "github.com" in a_cli_name.lower():
|
|
151
|
+
from machineconfig.utils.installer_utils.install_from_url import install_from_github_url
|
|
152
|
+
install_from_github_url(github_url=a_cli_name)
|
|
153
|
+
continue
|
|
146
154
|
selected_installer = None
|
|
147
155
|
for installer in all_installers:
|
|
148
156
|
app_name = installer["appName"]
|
|
149
|
-
if app_name.lower() ==
|
|
157
|
+
if app_name.lower() == a_cli_name.lower():
|
|
150
158
|
selected_installer = installer
|
|
151
159
|
break
|
|
152
160
|
if selected_installer is None:
|
|
153
|
-
_handle_installer_not_found(
|
|
161
|
+
_handle_installer_not_found(a_cli_name, all_names=[inst["appName"] for inst in all_installers])
|
|
154
162
|
return None
|
|
155
163
|
message = Installer(selected_installer).install_robust(version=None) # finish the task
|
|
156
164
|
total_messages.append(message)
|
|
157
165
|
if total_messages:
|
|
166
|
+
console = Console()
|
|
158
167
|
console.print("\n[bold green]📊 Installation Results:[/bold green]")
|
|
159
168
|
for a_message in total_messages:
|
|
160
169
|
console.print(f"[blue]• {a_message}[/blue]")
|
|
161
170
|
return None
|
|
162
|
-
|
|
163
|
-
|
|
164
171
|
def install_if_missing(which: str):
|
|
165
|
-
from machineconfig.utils.installer_utils.
|
|
172
|
+
from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
|
|
166
173
|
exists = check_tool_exists(which)
|
|
167
174
|
if exists:
|
|
168
175
|
print(f"✅ {which} is already installed.")
|
|
169
176
|
return
|
|
170
177
|
print(f"⏳ {which} not found. Installing...")
|
|
171
|
-
from machineconfig.utils.installer_utils.
|
|
172
|
-
|
|
178
|
+
from machineconfig.utils.installer_utils.installer_cli import main_installer_cli
|
|
179
|
+
main_installer_cli(which=which, interactive=False)
|
|
173
180
|
|
|
174
181
|
|
|
175
182
|
if __name__ == "__main__":
|
|
176
183
|
from machineconfig.utils.schemas.installer.installer_types import InstallerData
|
|
177
184
|
from machineconfig.utils.installer_utils.installer_class import Installer
|
|
178
|
-
|
|
179
185
|
_ = InstallerData, Installer
|
|
180
186
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""package manager"""
|
|
2
2
|
|
|
3
|
-
from machineconfig.utils.installer_utils.
|
|
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)
|
machineconfig/utils/meta.py
CHANGED
|
@@ -55,6 +55,16 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
55
55
|
import types as _types
|
|
56
56
|
from pathlib import Path as _Path
|
|
57
57
|
|
|
58
|
+
def _stringify_annotation(annotation: Any) -> Any:
|
|
59
|
+
if annotation is _inspect.Signature.empty or annotation is _inspect.Parameter.empty:
|
|
60
|
+
return annotation
|
|
61
|
+
if isinstance(annotation, str):
|
|
62
|
+
return annotation
|
|
63
|
+
try:
|
|
64
|
+
return _inspect.formatannotation(annotation)
|
|
65
|
+
except Exception:
|
|
66
|
+
return str(annotation)
|
|
67
|
+
|
|
58
68
|
# sanity checks
|
|
59
69
|
if not (callable(lmb) and isinstance(lmb, _types.LambdaType)):
|
|
60
70
|
raise TypeError("Expected a lambda function object")
|
|
@@ -174,16 +184,18 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
174
184
|
else:
|
|
175
185
|
new_default = param.default
|
|
176
186
|
|
|
177
|
-
|
|
187
|
+
normalized_annotation = _stringify_annotation(param.annotation)
|
|
188
|
+
|
|
178
189
|
if new_default is _inspect.Parameter.empty:
|
|
179
|
-
new_param = _inspect.Parameter(name, param.kind, annotation=
|
|
190
|
+
new_param = _inspect.Parameter(name, param.kind, annotation=normalized_annotation)
|
|
180
191
|
else:
|
|
181
192
|
new_param = _inspect.Parameter(
|
|
182
|
-
name, param.kind, default=new_default, annotation=
|
|
193
|
+
name, param.kind, default=new_default, annotation=normalized_annotation
|
|
183
194
|
)
|
|
184
195
|
new_params.append(new_param)
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
return_annotation = _stringify_annotation(sig.return_annotation)
|
|
198
|
+
new_sig = _inspect.Signature(parameters=new_params, return_annotation=return_annotation)
|
|
187
199
|
|
|
188
200
|
# If in_global mode, return kwargs as global assignments + dedented body
|
|
189
201
|
if in_global:
|
|
@@ -200,15 +212,11 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
200
212
|
|
|
201
213
|
# Build type annotation string if available
|
|
202
214
|
if param.annotation is not _inspect.Parameter.empty:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
type_str = str(param.annotation)
|
|
209
|
-
except Exception:
|
|
210
|
-
type_str = str(param.annotation)
|
|
211
|
-
global_assignments.append(f"{name}: {type_str} = {repr(value)}")
|
|
215
|
+
annotation_literal = _stringify_annotation(param.annotation)
|
|
216
|
+
if isinstance(annotation_literal, str):
|
|
217
|
+
global_assignments.append(f"{name}: {repr(annotation_literal)} = {repr(value)}")
|
|
218
|
+
else:
|
|
219
|
+
global_assignments.append(f"{name} = {repr(value)}")
|
|
212
220
|
else:
|
|
213
221
|
global_assignments.append(f"{name} = {repr(value)}")
|
|
214
222
|
|
|
@@ -233,10 +241,15 @@ def lambda_to_python_script(lmb: Callable[[], Any], in_global: bool, import_modu
|
|
|
233
241
|
|
|
234
242
|
if "Optional" in result_text or "Any" in result_text or "Union" in result_text or "Literal" in result_text:
|
|
235
243
|
result_text = "from typing import Optional, Any, Union, Literal\n\n" + result_text
|
|
236
|
-
|
|
237
244
|
if import_prefix:
|
|
238
245
|
result_text = f"{import_prefix}{result_text}"
|
|
239
246
|
return result_text
|
|
240
247
|
|
|
241
248
|
if __name__ == "__main__":
|
|
242
|
-
|
|
249
|
+
from machineconfig.utils.code import print_code
|
|
250
|
+
import_code_robust = "<import_code_robust>"
|
|
251
|
+
res = lambda_to_python_script(
|
|
252
|
+
lmb=lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
|
|
253
|
+
in_global=True, import_module=False
|
|
254
|
+
)
|
|
255
|
+
print(res)
|
machineconfig/utils/options.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from machineconfig.utils.installer_utils.
|
|
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
|
|
@@ -14,10 +14,10 @@ from typing import Optional, Union, Iterable, overload, Literal, cast
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@overload
|
|
17
|
-
def choose_from_options[T](
|
|
17
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
|
|
18
18
|
@overload
|
|
19
|
-
def choose_from_options[T](
|
|
20
|
-
def choose_from_options[T](
|
|
19
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> list[T]: ...
|
|
20
|
+
def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, ) -> Union[T, list[T]]:
|
|
21
21
|
# TODO: replace with https://github.com/tmbo/questionary
|
|
22
22
|
# # also see https://github.com/charmbracelet/gum
|
|
23
23
|
options_strings: list[str] = [str(x) for x in options]
|
|
@@ -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:
|
|
@@ -480,9 +480,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
480
480
|
**kwargs: Any,
|
|
481
481
|
) -> "PathExtended":
|
|
482
482
|
path_resolved, slf = self._resolve_path(folder, name, path, self.name).expanduser().resolve(), self.expanduser().resolve()
|
|
483
|
-
# if use_7z: # benefits over regular zip and encrypt: can handle very large files with low memory footprint
|
|
484
|
-
# path_resolved = path_resolved + '.7z' if not path_resolved.suffix == '.7z' else path_resolved
|
|
485
|
-
# with install_n_import("py7zr").SevenZipFile(file=path_resolved, mode=mode, password=pwd) as archive: archive.writeall(path=str(slf), arcname=None)
|
|
486
483
|
arcname_obj = PathExtended(arcname or slf.name)
|
|
487
484
|
if arcname_obj.name != slf.name:
|
|
488
485
|
arcname_obj /= slf.name # arcname has to start from somewhere and end with filename
|
|
@@ -555,15 +552,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
555
552
|
folder = folder if not content else folder.parent
|
|
556
553
|
if slf.suffix == ".7z":
|
|
557
554
|
raise NotImplementedError("I have not implemented this yet")
|
|
558
|
-
# if overwrite: P(folder).delete(sure=True)
|
|
559
|
-
# result = folder
|
|
560
|
-
# import py7zr
|
|
561
|
-
# with py7zr.SevenZipFile(file=slf, mode='r', password=pwd) as archive:
|
|
562
|
-
# if pattern is not None:
|
|
563
|
-
# import re
|
|
564
|
-
# pat = re.compile(pattern)
|
|
565
|
-
# archive.extract(path=folder, targets=[f for f in archive.getnames() if pat.match(f)])
|
|
566
|
-
# else: archive.extractall(path=folder)
|
|
567
555
|
else:
|
|
568
556
|
if overwrite:
|
|
569
557
|
if not content:
|
|
@@ -698,19 +686,52 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
698
686
|
return ret
|
|
699
687
|
|
|
700
688
|
def decompress(self, folder: OPLike = None, name: Optional[str] = None, path: OPLike = None, inplace: bool = False, orig: bool = False, verbose: bool = True) -> "PathExtended":
|
|
701
|
-
if ".tar.gz"
|
|
689
|
+
if str(self).endswith(".tar.gz") or str(self).endswith(".tgz"):
|
|
702
690
|
# res = self.ungz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
703
691
|
return self.ungz(name=f"tmp_{randstr()}.tar", inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose) # this works for .tgz suffix as well as .tar.gz
|
|
704
|
-
elif
|
|
692
|
+
elif str(self).endswith(".tar"):
|
|
693
|
+
res = self.untar(folder=folder, name=name, path=path, inplace=inplace, orig=orig, verbose=verbose)
|
|
694
|
+
elif str(self).endswith(".gz"):
|
|
705
695
|
res = self.ungz(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
706
|
-
elif ".tar.bz"
|
|
696
|
+
elif str(self).endswith(".tar.bz") or str(self).endswith(".tbz"):
|
|
707
697
|
res = self.unbz(name=f"tmp_{randstr()}.tar", inplace=inplace)
|
|
708
698
|
return res.untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
|
|
709
|
-
elif ".tar.xz"
|
|
699
|
+
elif str(self).endswith(".tar.xz"):
|
|
710
700
|
# res = self.unxz_untar(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
711
701
|
res = self.unxz(inplace=inplace).untar(folder=folder, name=name, path=path, inplace=True, orig=orig, verbose=verbose)
|
|
712
|
-
elif ".zip"
|
|
702
|
+
elif str(self).endswith(".zip"):
|
|
713
703
|
res = self.unzip(folder=folder, path=path, name=name, inplace=inplace, verbose=verbose, orig=orig)
|
|
704
|
+
elif str(self).endswith(".7z"):
|
|
705
|
+
def unzip_7z(archive_path: str, dest_dir: Optional[str] = None) -> Path:
|
|
706
|
+
"""
|
|
707
|
+
Uncompresses a .7z archive to a directory and returns the Path to the extraction directory.
|
|
708
|
+
|
|
709
|
+
:param archive_path: path to the .7z archive file
|
|
710
|
+
:param dest_dir: optional path to directory to extract into; if None a temporary dir will be created
|
|
711
|
+
:return: pathlib.Path pointing to the destination directory where contents were extracted
|
|
712
|
+
:raises: FileNotFoundError if archive does not exist; py7zr.Bad7zFile or other error if extraction fails
|
|
713
|
+
"""
|
|
714
|
+
import py7zr # type: ignore
|
|
715
|
+
import tempfile
|
|
716
|
+
from pathlib import Path
|
|
717
|
+
archive_path_obj = Path(archive_path)
|
|
718
|
+
if not archive_path_obj.is_file():
|
|
719
|
+
raise FileNotFoundError(f"Archive file not found: {archive_path_obj!r}")
|
|
720
|
+
if dest_dir is None:
|
|
721
|
+
# create a temporary directory
|
|
722
|
+
dest = Path(tempfile.mkdtemp(prefix=f"unzip7z_{archive_path_obj.stem}_"))
|
|
723
|
+
else:
|
|
724
|
+
dest = Path(dest_dir)
|
|
725
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
726
|
+
# Perform extraction
|
|
727
|
+
with py7zr.SevenZipFile(str(archive_path_obj), mode='r') as archive:
|
|
728
|
+
archive.extractall(path=str(dest))
|
|
729
|
+
# Return the extraction directory path
|
|
730
|
+
return dest
|
|
731
|
+
from machineconfig.utils.code import run_lambda_function
|
|
732
|
+
destination_dir = str(self.expanduser().resolve()).replace(".7z", "")
|
|
733
|
+
run_lambda_function(lambda: unzip_7z(archive_path=str(self), dest_dir=destination_dir), uv_project_dir=None, uv_with=["py7zr"])
|
|
734
|
+
res = PathExtended(destination_dir)
|
|
714
735
|
else:
|
|
715
736
|
res = self
|
|
716
737
|
return res
|
|
@@ -788,7 +809,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
|
|
|
788
809
|
path = self
|
|
789
810
|
else:
|
|
790
811
|
try:
|
|
791
|
-
path = self.
|
|
812
|
+
path = PathExtended(self.expanduser().absolute().relative_to(Path.home()))
|
|
792
813
|
except ValueError as ve:
|
|
793
814
|
if strict:
|
|
794
815
|
raise ve
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from machineconfig.utils.path_extended import PathExtended
|
|
2
1
|
from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
|
|
3
2
|
from rich.console import Console
|
|
4
3
|
from rich.panel import Panel
|
|
@@ -10,8 +9,8 @@ from typing import Optional
|
|
|
10
9
|
console = Console()
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
def sanitize_path(a_path: str) ->
|
|
14
|
-
path =
|
|
12
|
+
def sanitize_path(a_path: str) -> Path:
|
|
13
|
+
path = Path(a_path)
|
|
15
14
|
if Path.cwd() == Path.home() and not path.exists():
|
|
16
15
|
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
16
|
if result == "y":
|
|
@@ -22,13 +21,13 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
22
21
|
if platform.system() == "Windows": # path copied from Linux/Mac to Windows
|
|
23
22
|
# For Linux: /home/username, for Mac: /Users/username
|
|
24
23
|
skip_parts = 3 if path.as_posix().startswith("/home") else 3 # Both have 3 parts to skip
|
|
25
|
-
path =
|
|
24
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
26
25
|
assert path.exists(), f"File not found: {path}"
|
|
27
26
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
28
27
|
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
|
|
28
|
+
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
29
|
skip_parts = 3 # Both /home/username and /Users/username have 3 parts to skip
|
|
31
|
-
path =
|
|
30
|
+
path = Path.home().joinpath(*path.parts[skip_parts:])
|
|
32
31
|
assert path.exists(), f"File not found: {path}"
|
|
33
32
|
current_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
34
33
|
source_os = "Linux" if path.as_posix().startswith("/home") else "macOS"
|
|
@@ -36,12 +35,12 @@ def sanitize_path(a_path: str) -> PathExtended:
|
|
|
36
35
|
elif path.as_posix().startswith("C:"):
|
|
37
36
|
if platform.system() in ["Linux", "Darwin"]: # path copied from Windows to Linux/Mac
|
|
38
37
|
xx = str(a_path).replace("\\\\", "/")
|
|
39
|
-
path =
|
|
38
|
+
path = Path.home().joinpath(*Path(xx).parts[3:]) # exclude C:\\Users\\username
|
|
40
39
|
assert path.exists(), f"File not found: {path}"
|
|
41
40
|
target_os = "Linux" if platform.system() == "Linux" else "macOS"
|
|
42
41
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → {target_os}: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
43
|
-
elif platform.system() == "Windows" and
|
|
44
|
-
path =
|
|
42
|
+
elif platform.system() == "Windows" and Path.home().as_posix() not in path.as_posix(): # copied from Windows to Windows with different username
|
|
43
|
+
path = Path.home().joinpath(*path.parts[2:])
|
|
45
44
|
assert path.exists(), f"File not found: {path}"
|
|
46
45
|
console.print(Panel(f"🔗 PATH MAPPING | Windows → Windows: `{a_path}` ➡️ `{path}`", title="Path Mapping", expand=False))
|
|
47
46
|
return path.expanduser().absolute()
|
|
@@ -66,12 +65,12 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
|
|
|
66
65
|
return filename_matches, partial_path_matches
|
|
67
66
|
|
|
68
67
|
|
|
69
|
-
def match_file_name(sub_string: str, search_root:
|
|
68
|
+
def match_file_name(sub_string: str, search_root: Path, suffixes: set[str]) -> Path:
|
|
70
69
|
search_root_obj = search_root.absolute()
|
|
71
70
|
# assume subscript is filename only, not a sub_path. There is no need to fzf over the paths.
|
|
72
71
|
filename_matches, partial_path_matches = find_scripts(search_root_obj, sub_string, suffixes)
|
|
73
72
|
if len(filename_matches) == 1:
|
|
74
|
-
return
|
|
73
|
+
return Path(filename_matches[0])
|
|
75
74
|
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
75
|
if len(filename_matches) < 20:
|
|
77
76
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in filename_matches]))
|
|
@@ -80,23 +79,23 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
80
79
|
# let's see if avoiding .lower() helps narrowing down to one result
|
|
81
80
|
reduced_scripts = [a_potential_match for a_potential_match in filename_matches if sub_string in a_potential_match.name]
|
|
82
81
|
if len(reduced_scripts) == 1:
|
|
83
|
-
return
|
|
82
|
+
return Path(reduced_scripts[0])
|
|
84
83
|
elif len(reduced_scripts) > 1:
|
|
85
84
|
from machineconfig.utils.options import choose_from_options
|
|
86
85
|
choice = choose_from_options(multi=False, msg="Multiple matches found", options=reduced_scripts, fzf=True)
|
|
87
|
-
return
|
|
86
|
+
return Path(choice)
|
|
88
87
|
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
89
88
|
if len(reduced_scripts) < 10:
|
|
90
89
|
print("\n".join([a_potential_match.as_posix() for a_potential_match in reduced_scripts]))
|
|
91
90
|
|
|
92
91
|
console.print(Panel(f"Partial path match with case-insensitivity failed. This generated #{len(partial_path_matches)} results.", title="Search", expand=False))
|
|
93
92
|
if len(partial_path_matches) == 1:
|
|
94
|
-
return
|
|
93
|
+
return Path(partial_path_matches[0])
|
|
95
94
|
elif len(partial_path_matches) > 1:
|
|
96
95
|
print("Try to narrow down partial_path_matches search by case-sensitivity.")
|
|
97
96
|
reduced_scripts = [a_potential_match for a_potential_match in partial_path_matches if sub_string in a_potential_match.as_posix()]
|
|
98
97
|
if len(reduced_scripts) == 1:
|
|
99
|
-
return
|
|
98
|
+
return Path(reduced_scripts[0])
|
|
100
99
|
print(f"Result: This still generated {len(reduced_scripts)} results.")
|
|
101
100
|
|
|
102
101
|
try:
|
|
@@ -131,21 +130,24 @@ def match_file_name(sub_string: str, search_root: PathExtended, suffixes: set[st
|
|
|
131
130
|
return search_root_obj.joinpath(res)
|
|
132
131
|
|
|
133
132
|
|
|
134
|
-
def search_for_files_of_interest(path_obj:
|
|
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
|
|
133
|
+
def search_for_files_of_interest(path_obj: Path, suffixes: set[str]) -> list[Path]:
|
|
141
134
|
if path_obj.is_file():
|
|
142
135
|
return [path_obj]
|
|
143
|
-
files: list[
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
files: list[Path] = []
|
|
137
|
+
directories_to_visit: list[Path] = [path_obj]
|
|
138
|
+
while directories_to_visit:
|
|
139
|
+
current_dir = directories_to_visit.pop()
|
|
140
|
+
for entry in current_dir.iterdir():
|
|
141
|
+
if entry.is_dir():
|
|
142
|
+
if entry.name == ".venv":
|
|
143
|
+
continue
|
|
144
|
+
directories_to_visit.append(entry)
|
|
145
|
+
continue
|
|
146
|
+
if entry.suffix not in suffixes:
|
|
147
|
+
continue
|
|
148
|
+
if entry.suffix == ".py" and entry.name == "__init__.py":
|
|
149
|
+
continue
|
|
150
|
+
files.append(entry)
|
|
149
151
|
return files
|
|
150
152
|
|
|
151
153
|
|
|
@@ -160,15 +162,15 @@ def get_choice_file(path: str, suffixes: Optional[set[str]]):
|
|
|
160
162
|
else:
|
|
161
163
|
suffixes = {".py"}
|
|
162
164
|
if not path_obj.exists():
|
|
163
|
-
print(f"🔍 Searching for file matching `{path}` under `{
|
|
164
|
-
choice_file = match_file_name(sub_string=path, search_root=
|
|
165
|
+
print(f"🔍 Searching for file matching `{path}` under `{Path.cwd()}`, but only if suffix matches {suffixes}")
|
|
166
|
+
choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
|
|
165
167
|
elif path_obj.is_dir():
|
|
166
168
|
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
167
169
|
files = search_for_files_of_interest(path_obj, suffixes=suffixes)
|
|
168
170
|
print(f"🔍 Got #{len(files)} results.")
|
|
169
171
|
from machineconfig.utils.options import choose_from_options
|
|
170
172
|
choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
|
|
171
|
-
choice_file =
|
|
173
|
+
choice_file = Path(choice_file)
|
|
172
174
|
else:
|
|
173
175
|
choice_file = path_obj
|
|
174
176
|
return choice_file
|