machineconfig 5.12__py3-none-any.whl → 5.14__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/jobs/python/check_installations.py +0 -13
- machineconfig/profile/create.py +127 -13
- machineconfig/scripts/python/count_lines.py +9 -5
- machineconfig/scripts/python/count_lines_frontend.py +2 -2
- machineconfig/scripts/python/croshell.py +20 -67
- machineconfig/scripts/python/interactive.py +2 -2
- machineconfig/scripts/python/repos.py +2 -2
- machineconfig/utils/files/ascii_art.py +118 -0
- machineconfig/utils/files/headers.py +64 -0
- machineconfig/utils/files/read.py +103 -0
- machineconfig/utils/installer_utils/installer_abc.py +7 -0
- machineconfig/utils/links.py +133 -16
- machineconfig/utils/options.py +0 -25
- {machineconfig-5.12.dist-info → machineconfig-5.14.dist-info}/METADATA +1 -1
- {machineconfig-5.12.dist-info → machineconfig-5.14.dist-info}/RECORD +18 -18
- machineconfig/jobs/python/create_bootable_media.py +0 -16
- machineconfig/jobs/python/python_cargo_build_share.py +0 -58
- machineconfig/jobs/python/tasks.py +0 -3
- {machineconfig-5.12.dist-info → machineconfig-5.14.dist-info}/WHEEL +0 -0
- {machineconfig-5.12.dist-info → machineconfig-5.14.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.12.dist-info → machineconfig-5.14.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
# """CI
|
|
2
|
-
# """
|
|
3
1
|
|
|
4
|
-
|
|
5
|
-
# import time
|
|
6
2
|
import platform
|
|
7
|
-
|
|
8
|
-
# from typing import Any
|
|
9
|
-
# from rich.console import Console
|
|
10
|
-
# from machineconfig.utils.utils2 import pprint
|
|
11
|
-
# # from rich.progress import track
|
|
12
3
|
from machineconfig.utils.source_of_truth import LIBRARY_ROOT
|
|
13
|
-
# from machineconfig.utils.installer import get_installed_cli_apps
|
|
14
|
-
# from typing import Optional
|
|
15
|
-
# from datetime import datetime
|
|
16
|
-
# import csv
|
|
17
4
|
|
|
18
5
|
|
|
19
6
|
APP_SUMMARY_PATH = LIBRARY_ROOT.joinpath(f"profile/records/{platform.system().lower()}/apps_summary_report.csv")
|
machineconfig/profile/create.py
CHANGED
|
@@ -8,6 +8,7 @@ from rich.console import Console
|
|
|
8
8
|
from rich.panel import Panel
|
|
9
9
|
from rich.pretty import Pretty
|
|
10
10
|
from rich.text import Text
|
|
11
|
+
from rich.table import Table
|
|
11
12
|
|
|
12
13
|
from machineconfig.utils.path_extended import PathExtended
|
|
13
14
|
from machineconfig.utils.links import symlink_func, symlink_copy
|
|
@@ -20,7 +21,7 @@ import os
|
|
|
20
21
|
import ctypes
|
|
21
22
|
import subprocess
|
|
22
23
|
import tomllib
|
|
23
|
-
from typing import Optional, Any, TypedDict
|
|
24
|
+
from typing import Optional, Any, TypedDict, Literal
|
|
24
25
|
|
|
25
26
|
system = platform.system() # Linux or Windows
|
|
26
27
|
ERROR_LIST: list[Any] = [] # append to this after every exception captured.
|
|
@@ -43,10 +44,35 @@ class SymlinkMapper(TypedDict):
|
|
|
43
44
|
contents: Optional[bool]
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
class OperationRecord(TypedDict):
|
|
48
|
+
program: str
|
|
49
|
+
file_key: str
|
|
50
|
+
source: str
|
|
51
|
+
target: str
|
|
52
|
+
operation: str
|
|
53
|
+
action: Literal[
|
|
54
|
+
"already_linked",
|
|
55
|
+
"relinking",
|
|
56
|
+
"fixing_broken_link",
|
|
57
|
+
"identical_files",
|
|
58
|
+
"backing_up_source",
|
|
59
|
+
"backing_up_target",
|
|
60
|
+
"relinking_to_new_target",
|
|
61
|
+
"moving_to_target",
|
|
62
|
+
"new_link",
|
|
63
|
+
"new_link_and_target",
|
|
64
|
+
"linking",
|
|
65
|
+
"copying",
|
|
66
|
+
"error"
|
|
67
|
+
]
|
|
68
|
+
details: str
|
|
69
|
+
status: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def apply_mapper(choice: Optional[str], prioritize_to_this: bool):
|
|
47
73
|
symlink_mapper: dict[str, dict[str, SymlinkMapper]] = tomllib.loads(LIBRARY_ROOT.joinpath("profile/mapper.toml").read_text(encoding="utf-8"))
|
|
48
|
-
prioritize_to_this = True
|
|
49
74
|
exclude: list[str] = [] # "wsl_linux", "wsl_windows"
|
|
75
|
+
operation_records: list[OperationRecord] = []
|
|
50
76
|
|
|
51
77
|
program_keys_raw: list[str] = list(symlink_mapper.keys())
|
|
52
78
|
program_keys: list[str] = []
|
|
@@ -64,10 +90,6 @@ def apply_mapper(choice: Optional[str] = None):
|
|
|
64
90
|
return # terminate function.
|
|
65
91
|
elif len(choice_selected) == 1 and choice_selected[0] == "all":
|
|
66
92
|
choice_selected = "all" # i.e. program_keys = program_keys
|
|
67
|
-
# overwrite = choose_from_options(msg="Overwrite existing source file?", options=["yes", "no"], default="yes") == "yes"
|
|
68
|
-
from rich.prompt import Confirm
|
|
69
|
-
|
|
70
|
-
prioritize_to_this = Confirm.ask("Overwrite existing source file?", default=True)
|
|
71
93
|
else:
|
|
72
94
|
choice_selected = choice
|
|
73
95
|
|
|
@@ -114,22 +136,85 @@ def apply_mapper(choice: Optional[str] = None):
|
|
|
114
136
|
for file_key, file_map in symlink_mapper[program_key].items():
|
|
115
137
|
this = PathExtended(file_map["this"])
|
|
116
138
|
to_this = PathExtended(file_map["to_this"].replace("REPO_ROOT", REPO_ROOT.as_posix()).replace("LIBRARY_ROOT", LIBRARY_ROOT.as_posix()))
|
|
139
|
+
|
|
117
140
|
if "contents" in file_map:
|
|
118
141
|
try:
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
targets = list(to_this.expanduser().search("*"))
|
|
143
|
+
for a_target in targets:
|
|
144
|
+
result = symlink_func(this=this.joinpath(a_target.name), to_this=a_target, prioritize_to_this=prioritize_to_this)
|
|
145
|
+
operation_records.append({
|
|
146
|
+
"program": program_key,
|
|
147
|
+
"file_key": file_key,
|
|
148
|
+
"source": str(this.joinpath(a_target.name)),
|
|
149
|
+
"target": str(a_target),
|
|
150
|
+
"operation": "contents_symlink",
|
|
151
|
+
"action": result["action"],
|
|
152
|
+
"details": result["details"],
|
|
153
|
+
"status": "success"
|
|
154
|
+
})
|
|
121
155
|
except Exception as ex:
|
|
122
156
|
console.print(f"❌ [red]Config error[/red]: {program_key} | {file_key} | missing keys 'this ==> to_this'. {ex}")
|
|
123
|
-
|
|
157
|
+
operation_records.append({
|
|
158
|
+
"program": program_key,
|
|
159
|
+
"file_key": file_key,
|
|
160
|
+
"source": str(this),
|
|
161
|
+
"target": str(to_this),
|
|
162
|
+
"operation": "contents_symlink",
|
|
163
|
+
"action": "error",
|
|
164
|
+
"details": f"Failed to process contents: {str(ex)}",
|
|
165
|
+
"status": f"error: {str(ex)}"
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
elif "copy" in file_map:
|
|
124
169
|
try:
|
|
125
|
-
symlink_copy(this=this, to_this=to_this, prioritize_to_this=prioritize_to_this)
|
|
170
|
+
result = symlink_copy(this=this, to_this=to_this, prioritize_to_this=prioritize_to_this)
|
|
171
|
+
operation_records.append({
|
|
172
|
+
"program": program_key,
|
|
173
|
+
"file_key": file_key,
|
|
174
|
+
"source": str(this),
|
|
175
|
+
"target": str(to_this),
|
|
176
|
+
"operation": "copy",
|
|
177
|
+
"action": result["action"],
|
|
178
|
+
"details": result["details"],
|
|
179
|
+
"status": "success"
|
|
180
|
+
})
|
|
126
181
|
except Exception as ex:
|
|
127
182
|
console.print(f"❌ [red]Config error[/red]: {program_key} | {file_key} | {ex}")
|
|
183
|
+
operation_records.append({
|
|
184
|
+
"program": program_key,
|
|
185
|
+
"file_key": file_key,
|
|
186
|
+
"source": str(this),
|
|
187
|
+
"target": str(to_this),
|
|
188
|
+
"operation": "copy",
|
|
189
|
+
"action": "error",
|
|
190
|
+
"details": f"Failed to copy: {str(ex)}",
|
|
191
|
+
"status": f"error: {str(ex)}"
|
|
192
|
+
})
|
|
128
193
|
else:
|
|
129
194
|
try:
|
|
130
|
-
symlink_func(this=this, to_this=to_this, prioritize_to_this=prioritize_to_this)
|
|
195
|
+
result = symlink_func(this=this, to_this=to_this, prioritize_to_this=prioritize_to_this)
|
|
196
|
+
operation_records.append({
|
|
197
|
+
"program": program_key,
|
|
198
|
+
"file_key": file_key,
|
|
199
|
+
"source": str(this),
|
|
200
|
+
"target": str(to_this),
|
|
201
|
+
"operation": "symlink",
|
|
202
|
+
"action": result["action"],
|
|
203
|
+
"details": result["details"],
|
|
204
|
+
"status": "success"
|
|
205
|
+
})
|
|
131
206
|
except Exception as ex:
|
|
132
207
|
console.print(f"❌ [red]Config error[/red]: {program_key} | {file_key} | missing keys 'this ==> to_this'. {ex}")
|
|
208
|
+
operation_records.append({
|
|
209
|
+
"program": program_key,
|
|
210
|
+
"file_key": file_key,
|
|
211
|
+
"source": str(this),
|
|
212
|
+
"target": str(to_this),
|
|
213
|
+
"operation": "symlink",
|
|
214
|
+
"action": "error",
|
|
215
|
+
"details": f"Failed to create symlink: {str(ex)}",
|
|
216
|
+
"status": f"error: {str(ex)}"
|
|
217
|
+
})
|
|
133
218
|
|
|
134
219
|
if program_key == "ssh" and system == "Linux": # permissions of ~/dotfiles/.ssh should be adjusted
|
|
135
220
|
try:
|
|
@@ -147,6 +232,35 @@ def apply_mapper(choice: Optional[str] = None):
|
|
|
147
232
|
subprocess.run(f"chmod +x {LIBRARY_ROOT.joinpath(f'scripts/{system.lower()}')} -R", shell=True, capture_output=True, text=True)
|
|
148
233
|
console.print("[green]✅ Script permissions updated[/green]")
|
|
149
234
|
|
|
235
|
+
# Display operation summary table
|
|
236
|
+
if operation_records:
|
|
237
|
+
table = Table(title="🔗 Symlink Operations Summary", show_header=True, header_style="bold magenta")
|
|
238
|
+
table.add_column("Program", style="cyan", no_wrap=True)
|
|
239
|
+
table.add_column("File Key", style="blue", no_wrap=True)
|
|
240
|
+
table.add_column("Source", style="green")
|
|
241
|
+
table.add_column("Target", style="yellow")
|
|
242
|
+
table.add_column("Operation", style="magenta", no_wrap=True)
|
|
243
|
+
table.add_column("Action", style="red", no_wrap=True)
|
|
244
|
+
table.add_column("Details", style="white")
|
|
245
|
+
table.add_column("Status", style="red", no_wrap=True)
|
|
246
|
+
|
|
247
|
+
for record in operation_records:
|
|
248
|
+
status_style = "green" if record["status"] == "success" else "red"
|
|
249
|
+
action_style = "green" if record["action"] != "error" else "red"
|
|
250
|
+
table.add_row(
|
|
251
|
+
record["program"],
|
|
252
|
+
record["file_key"],
|
|
253
|
+
record["source"],
|
|
254
|
+
record["target"],
|
|
255
|
+
record["operation"],
|
|
256
|
+
f"[{action_style}]{record['action']}[/{action_style}]",
|
|
257
|
+
record["details"],
|
|
258
|
+
f"[{status_style}]{record['status']}[/{status_style}]"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
console.print("\n")
|
|
262
|
+
console.print(table)
|
|
263
|
+
|
|
150
264
|
if len(ERROR_LIST) > 0:
|
|
151
265
|
console.print(
|
|
152
266
|
Panel(
|
|
@@ -169,7 +283,7 @@ def apply_mapper(choice: Optional[str] = None):
|
|
|
169
283
|
def main_symlinks():
|
|
170
284
|
console.print("")
|
|
171
285
|
console.rule("[bold blue]🔗 CREATING SYMLINKS 🔗")
|
|
172
|
-
apply_mapper(choice="all")
|
|
286
|
+
apply_mapper(choice="all", prioritize_to_this=True)
|
|
173
287
|
|
|
174
288
|
|
|
175
289
|
def main_profile():
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
from git import Repo
|
|
@@ -7,15 +6,12 @@ from datetime import datetime
|
|
|
7
6
|
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from rich.progress import track
|
|
10
|
-
import polars as pl
|
|
11
|
-
import plotly.graph_objects as go
|
|
12
|
-
|
|
13
|
-
import plotly.express as px
|
|
14
9
|
import typer
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
if TYPE_CHECKING:
|
|
18
13
|
from typing import Any, Dict, List, Optional, Union
|
|
14
|
+
import polars as pl
|
|
19
15
|
|
|
20
16
|
|
|
21
17
|
app = typer.Typer()
|
|
@@ -110,6 +106,10 @@ def analyze_over_time(repo_path: str = typer.Argument(..., help="Path to the git
|
|
|
110
106
|
except Exception as e:
|
|
111
107
|
print(f"❌ Error analyzing commits: {str(e)}")
|
|
112
108
|
return
|
|
109
|
+
|
|
110
|
+
import polars as pl
|
|
111
|
+
import plotly.graph_objects as go
|
|
112
|
+
|
|
113
113
|
df = pl.DataFrame(commit_data)
|
|
114
114
|
df = df.sort("dtmExit")
|
|
115
115
|
# Create interactive plotly figure with dark theme and all bells and whistles
|
|
@@ -184,6 +184,10 @@ def analyze_over_time(repo_path: str = typer.Argument(..., help="Path to the git
|
|
|
184
184
|
|
|
185
185
|
|
|
186
186
|
def _print_python_files_by_size_impl(repo_path: str) -> "Union[pl.DataFrame, Exception]":
|
|
187
|
+
import polars as pl
|
|
188
|
+
import plotly.graph_objects as go
|
|
189
|
+
import plotly.express as px
|
|
190
|
+
|
|
187
191
|
try:
|
|
188
192
|
import os
|
|
189
193
|
if not os.path.exists(repo_path):
|
|
@@ -5,9 +5,9 @@ import typer
|
|
|
5
5
|
def analyze_repo_development(repo_path: str = typer.Argument(..., help="Path to the git repository")):
|
|
6
6
|
from machineconfig.scripts.python import count_lines
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
count_lines_path = Path(count_lines.__file__)
|
|
8
|
+
count_lines_path = Path(count_lines.__file__)
|
|
9
9
|
# --project $HOME/code/machineconfig
|
|
10
|
-
cmd = f"""uv run --python 3.13 --with machineconfig
|
|
10
|
+
cmd = f"""uv run --python 3.13 --with machineconfig[plot] {count_lines_path} analyze-over-time {repo_path}"""
|
|
11
11
|
from machineconfig.utils.code import run_script
|
|
12
12
|
run_script(cmd)
|
|
13
13
|
|
|
@@ -40,8 +40,6 @@ except Exception: print(pycode)
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def get_read_data_pycode(path: str):
|
|
43
|
-
# We need to be careful here since we're generating Python code as a string
|
|
44
|
-
# that will use f-strings itself
|
|
45
43
|
return f"""
|
|
46
44
|
from rich.panel import Panel
|
|
47
45
|
from rich.text import Text
|
|
@@ -49,7 +47,8 @@ from rich.console import Console
|
|
|
49
47
|
console = Console()
|
|
50
48
|
p = PathExtended(r'{path}').absolute()
|
|
51
49
|
try:
|
|
52
|
-
|
|
50
|
+
from machineconfig.utils.files.read import Read
|
|
51
|
+
dat = Read.read(p)
|
|
53
52
|
if isinstance(dat, dict):
|
|
54
53
|
panel_title = f"📄 File Data: {{p.name}}"
|
|
55
54
|
console.print(Panel(Text(str(dat), justify="left"), title=panel_title, expand=False))
|
|
@@ -63,53 +62,25 @@ except Exception as e:
|
|
|
63
62
|
"""
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
def get_read_pyfile_pycode(path: PathExtended, as_module: bool, cmd: str = ""):
|
|
67
|
-
if as_module:
|
|
68
|
-
pycode = rf"""
|
|
69
|
-
import sys
|
|
70
|
-
sys.path.append(r'{path.parent}')
|
|
71
|
-
from {path.stem} import *
|
|
72
|
-
{cmd}
|
|
73
|
-
"""
|
|
74
|
-
else:
|
|
75
|
-
pycode = f"""
|
|
76
|
-
__file__ = PathExtended(r'{path}')
|
|
77
|
-
{path.read_text(encoding="utf-8")}
|
|
78
|
-
"""
|
|
79
|
-
return pycode
|
|
80
|
-
|
|
81
65
|
|
|
82
66
|
def main(
|
|
83
|
-
module: Annotated[bool, typer.Option("--module", "-m", help="flag to run the file as a module as opposed to main.")] = False,
|
|
84
|
-
newWindow: Annotated[bool, typer.Option("--newWindow", "-w", help="flag for running in new window.")] = False,
|
|
85
|
-
nonInteratctive: Annotated[bool, typer.Option("--nonInteratctive", "-N", help="flag for a non-interactive session.")] = False,
|
|
86
67
|
python: Annotated[bool, typer.Option("--python", "-p", help="flag to use python over IPython.")] = False,
|
|
87
68
|
fzf: Annotated[bool, typer.Option("--fzf", "-F", help="search with fuzzy finder for python scripts and run them")] = False,
|
|
88
69
|
ve: Annotated[Optional[str], typer.Option("--ve", "-v", help="virtual enviroment to use, defaults to activated ve, if existed, else ve.")] = None,
|
|
89
70
|
profile: Annotated[Optional[str], typer.Option("--profile", "-P", help="ipython profile to use, defaults to default profile.")] = None,
|
|
90
71
|
read: Annotated[str, typer.Option("--read", "-r", help="read a binary file.")] = "",
|
|
91
|
-
file: Annotated[str, typer.Option("--file", "-f", help="python file path to interpret")] = "",
|
|
92
|
-
cmd: Annotated[str, typer.Option("--cmd", "-c", help="python command to interpret")] = "",
|
|
93
|
-
terminal: Annotated[str, typer.Option("--terminal", "-t", help="specify which terminal to be used. Default console host.")] = "",
|
|
94
|
-
shell: Annotated[str, typer.Option("--shell", "-S", help="specify which shell to be used. Defaults to CMD.")] = "",
|
|
95
72
|
jupyter: Annotated[bool, typer.Option("--jupyter", "-j", help="run in jupyter interactive console")] = False,
|
|
96
73
|
streamlit_viewer: Annotated[bool, typer.Option("--stViewer", "-s", help="view in streamlit app")] = False,
|
|
74
|
+
visidata: Annotated[bool, typer.Option("--visidata", "-V", help="open data file in visidata")] = False,
|
|
97
75
|
) -> None:
|
|
98
76
|
# ==================================================================================
|
|
99
77
|
# flags processing
|
|
100
|
-
interactivity = "
|
|
78
|
+
interactivity = "-i"
|
|
101
79
|
interpreter = "python" if python else "ipython"
|
|
102
80
|
ipython_profile: Optional[str] = profile
|
|
103
81
|
file_obj = PathExtended.cwd() # initialization value, could be modified according to args.
|
|
104
82
|
|
|
105
|
-
if
|
|
106
|
-
text = "🖥️ Executing command from CLI argument"
|
|
107
|
-
console.print(Panel(text, title="[bold blue]Info[/bold blue]"))
|
|
108
|
-
import textwrap
|
|
109
|
-
|
|
110
|
-
program = textwrap.dedent(cmd)
|
|
111
|
-
|
|
112
|
-
elif fzf:
|
|
83
|
+
if fzf:
|
|
113
84
|
text = "🔍 Searching for Python files..."
|
|
114
85
|
console.print(Panel(text, title="[bold blue]Info[/bold blue]"))
|
|
115
86
|
options = [str(item) for item in PathExtended.cwd().search("*.py", r=True)]
|
|
@@ -119,13 +90,6 @@ def main(
|
|
|
119
90
|
text = f"📄 Selected file: {PathExtended(file_selected).name}"
|
|
120
91
|
console.print(Panel(text, title="[bold blue]Info[/bold blue]"))
|
|
121
92
|
|
|
122
|
-
elif file != "":
|
|
123
|
-
file_obj = PathExtended(file.lstrip()).expanduser().absolute()
|
|
124
|
-
program = get_read_pyfile_pycode(file_obj, as_module=module, cmd=cmd)
|
|
125
|
-
text1 = f"📄 Loading file: {file_obj.name}"
|
|
126
|
-
text2 = f"🔄 Mode: {'Module' if module else 'Script'}"
|
|
127
|
-
console.print(Panel(f"{text1}\n{text2}", title="[bold blue]Info[/bold blue]"))
|
|
128
|
-
|
|
129
93
|
elif read != "":
|
|
130
94
|
if streamlit_viewer:
|
|
131
95
|
# text = "📊 STARTING STREAMLIT VIEWER"
|
|
@@ -154,46 +118,35 @@ def main(
|
|
|
154
118
|
preprogram = """
|
|
155
119
|
|
|
156
120
|
#%%
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
except ImportError:
|
|
162
|
-
print("Crocodile not found, skipping import.")
|
|
121
|
+
|
|
122
|
+
from machineconfig.utils.files.headers import print_header, print_logo
|
|
123
|
+
print_header()
|
|
124
|
+
print_logo("CROCODILE")
|
|
163
125
|
from pathlib import Path
|
|
164
|
-
|
|
126
|
+
|
|
165
127
|
"""
|
|
166
128
|
|
|
167
129
|
pyfile = PathExtended.tmp().joinpath(f"tmp_scripts/python/croshell/{randstr()}.py")
|
|
168
130
|
pyfile.parent.mkdir(parents=True, exist_ok=True)
|
|
169
131
|
|
|
170
|
-
|
|
171
|
-
title = "Reading Data"
|
|
172
|
-
elif file != "":
|
|
173
|
-
title = "Running Python File"
|
|
174
|
-
else:
|
|
175
|
-
title = "Executed code"
|
|
132
|
+
title = "Reading Data"
|
|
176
133
|
python_program = preprogram + add_print_header_pycode(str(pyfile), title=title) + program
|
|
177
134
|
pyfile.write_text(python_program, encoding="utf-8")
|
|
178
135
|
# ve_root_from_file, ipython_profile = get_ve_path_and_ipython_profile(PathExtended(file))
|
|
179
136
|
ipython_profile = ipython_profile if ipython_profile is not None else "default"
|
|
180
137
|
# ve_activateion_line = get_ve_activate_line(ve_name=args.ve or ve_profile_suggested, a_path=str(PathExtended.cwd()))
|
|
181
|
-
shell_program = """
|
|
182
|
-
#!/bin/bash
|
|
183
138
|
|
|
184
|
-
|
|
185
|
-
|
|
139
|
+
if visidata:
|
|
140
|
+
fire_line = f"uv run --with visidata,pyarrow vd {str(file_obj)}"
|
|
141
|
+
elif jupyter:
|
|
186
142
|
fire_line = f"code --new-window {str(pyfile)}"
|
|
187
143
|
else:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
fire_line
|
|
191
|
-
|
|
192
|
-
from
|
|
193
|
-
|
|
194
|
-
print()
|
|
195
|
-
import subprocess
|
|
196
|
-
subprocess.run(shell_program, shell=True, check=True)
|
|
144
|
+
if interpreter == "ipython": profile = f" --profile {ipython_profile} --no-banner"
|
|
145
|
+
else: profile = ""
|
|
146
|
+
fire_line = f"uv run --python 3.13 --with machineconfig[plot] {interpreter} {interactivity} {profile} {str(pyfile)}"
|
|
147
|
+
|
|
148
|
+
from machineconfig.utils.code import run_script
|
|
149
|
+
run_script(fire_line)
|
|
197
150
|
|
|
198
151
|
|
|
199
152
|
def arg_parser() -> None:
|
|
@@ -186,8 +186,8 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
|
|
|
186
186
|
|
|
187
187
|
if "retrieve_repositories" in selected_options:
|
|
188
188
|
console.print(Panel("📚 [bold bright_magenta]REPOSITORIES[/bold bright_magenta]\n[italic]Project code retrieval[/italic]", border_style="bright_magenta"))
|
|
189
|
-
from machineconfig.scripts.python import repos
|
|
190
|
-
|
|
189
|
+
from machineconfig.scripts.python import repos
|
|
190
|
+
repos.clone(directory=str(Path.home() / "code"), cloud="odg1")
|
|
191
191
|
|
|
192
192
|
if "retrieve_data" in selected_options:
|
|
193
193
|
console.print(Panel("💾 [bold bright_cyan]DATA RETRIEVAL[/bold bright_cyan]\n[italic]Backup restoration[/italic]", border_style="bright_cyan"))
|
|
@@ -73,7 +73,7 @@ def all(
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
@sync_app.command()
|
|
76
|
-
def
|
|
76
|
+
def capture(
|
|
77
77
|
directory: DirectoryArgument = None,
|
|
78
78
|
cloud: CloudOption = None,
|
|
79
79
|
) -> None:
|
|
@@ -87,7 +87,7 @@ def record(
|
|
|
87
87
|
if cloud is not None:
|
|
88
88
|
PathExtended(save_path).to_cloud(rel2home=True, cloud=cloud)
|
|
89
89
|
@sync_app.command()
|
|
90
|
-
def
|
|
90
|
+
def clone(
|
|
91
91
|
directory: DirectoryArgument = None,
|
|
92
92
|
cloud: CloudOption = None,
|
|
93
93
|
) -> None:
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Ascii art
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import textwrap
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import tempfile
|
|
10
|
+
import platform
|
|
11
|
+
from typing import Optional, Literal
|
|
12
|
+
|
|
13
|
+
# https://github.com/sepandhaghighi/art
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
BOX_OR_CHAR = Literal['boxes', 'cowsay']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ArtLib:
|
|
20
|
+
@staticmethod
|
|
21
|
+
def cowsay(text: str):
|
|
22
|
+
import cowsay
|
|
23
|
+
char = random.choice(cowsay.char_names)
|
|
24
|
+
return cowsay.get_output_string(char, text=text)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BoxStyles:
|
|
28
|
+
language = ['ada-box', 'caml', 'boxquote', 'stone', 'tex-box', 'shell', 'simple', 'c', 'cc', 'html']
|
|
29
|
+
scene = ['whirly', 'xes', 'columns', 'parchment', 'scroll', 'scroll-akn', 'diamonds', 'headline', 'nuke', 'spring', 'stark1'] # , 'important3'
|
|
30
|
+
character = ['capgirl', 'cat', 'boy', 'girl', 'dog', 'mouse', 'santa', 'face', 'ian_jones', 'peek', 'unicornsay']
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CowStyles:
|
|
34
|
+
eyes = ['-b', '-d', '-g', '-h', '-l', '-L', '-n', '-N', '-p', '-s', '-t', '-w', '-y']
|
|
35
|
+
# this one for the package installed with sudo apt install cowsay and is located at /usr/games/cowsay. See cowsay -l
|
|
36
|
+
figures = ['apt', 'bunny', 'cheese', 'cock', 'cower', 'daemon', 'default', 'dragon',
|
|
37
|
+
'dragon-and-cow', 'duck', 'elephant', 'elephant-in-snake', 'eyes', 'fox', 'ghostbusters',
|
|
38
|
+
'gnu', 'kangaroo', 'kiss', 'milk',
|
|
39
|
+
'moose', 'pony', 'pony-smaller', 'sheep', 'skeleton', 'snowman', 'stegosaurus', # 'suse',
|
|
40
|
+
'three-eyes', 'turkey', 'turtle', 'tux', 'unipony', 'unipony-smaller', 'vader', 'vader'] # 'hellokitty' 'mech-and-cow' # 'moofasa', 'stimpy', 'calvin', , 'ren', 'koala', 'flaming-sheep' , 'bud-frogs' , 'kosh' , 'luke-koala'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
FIGLET_FONTS = ['banner', 'big', 'standard']
|
|
44
|
+
|
|
45
|
+
FIGJS_FONTS = ['3D Diagonal', '3D-ASCII', '4Max', '5 Line Oblique', 'Acrobatic', 'ANSI Regular', 'ANSI Shadow',
|
|
46
|
+
'Avatar', 'Banner', 'Banner3-D', 'Banner4',
|
|
47
|
+
'Basic', 'Big Money-ne', 'Big Money-nw', 'Big Money-se', 'Big Money-sw', 'Big', 'Bloody', 'Bolger', 'Braced', 'Bright',
|
|
48
|
+
'DOS Rebel',
|
|
49
|
+
'Elite', 'Epic', 'Flower Power',
|
|
50
|
+
'Fraktur', # 'Isometric4'. 'AMC Tubes', 'Banner3', Alligator2
|
|
51
|
+
'Star Wars',
|
|
52
|
+
'Sub-Zero', 'The Edge', 'USA Flag', 'Varsity', "Doom"
|
|
53
|
+
] # too large Crazy 'Sweet', 'Electronic', 'Swamp Land', Crawford, Alligator
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_art(comment: Optional[str] = None, artlib: Optional[BOX_OR_CHAR] = None, style: Optional[str] = None, super_style: str = 'scene', prefix: str = ' ', file: Optional[str] = None, verbose: bool = True):
|
|
57
|
+
""" takes in a comment and does the following wrangling:
|
|
58
|
+
* text => figlet font => boxes => lolcat
|
|
59
|
+
* text => cowsay => lolcat
|
|
60
|
+
"""
|
|
61
|
+
if comment is None:
|
|
62
|
+
try:
|
|
63
|
+
comment = subprocess.run("fortune", shell=True, capture_output=True, text=True, check=True).stdout
|
|
64
|
+
except Exception:
|
|
65
|
+
comment = "crocodile"
|
|
66
|
+
if artlib is None: artlib = random.choice(['boxes', 'cowsay'])
|
|
67
|
+
to_file = '' if not file else f'> {file}'
|
|
68
|
+
if artlib == 'boxes':
|
|
69
|
+
if style is None: style = random.choice(BoxStyles.__dict__[super_style or random.choice(['language', 'scene', 'character'])])
|
|
70
|
+
fonting = f'figlet -f {random.choice(FIGLET_FONTS)}'
|
|
71
|
+
cmd = f"""echo "{comment}" | {fonting} | boxes -d {style} {to_file}"""
|
|
72
|
+
else:
|
|
73
|
+
if style is None: style = random.choice(CowStyles.figures)
|
|
74
|
+
cmd = f"""echo "{comment}" | /usr/games/cowsay -f {style} {to_file}"""
|
|
75
|
+
try:
|
|
76
|
+
res = subprocess.run(cmd, text=True, capture_output=True, shell=True, check=True).stdout
|
|
77
|
+
except subprocess.CalledProcessError as ex:
|
|
78
|
+
print(ex)
|
|
79
|
+
return ""
|
|
80
|
+
res = textwrap.indent(res, prefix=prefix)
|
|
81
|
+
if verbose:
|
|
82
|
+
print(f'Using style: {style} from {artlib}', '\n' * 3)
|
|
83
|
+
print(f'{cmd=}')
|
|
84
|
+
print('Results:\n', res)
|
|
85
|
+
return res
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def font_box_color(logo: str):
|
|
89
|
+
font = random.choice(FIGJS_FONTS)
|
|
90
|
+
# print(f"{font}\n")
|
|
91
|
+
box_style = random.choice(['whirly', 'xes', 'columns', 'parchment', 'scroll', 'scroll-akn', 'diamonds', 'headline', 'nuke', 'spring', 'stark1'])
|
|
92
|
+
_cmd = f'figlet -f "{font}" "{logo}" | boxes -d "{box_style}" | lolcatjs'
|
|
93
|
+
# print(_cmd)
|
|
94
|
+
os.system(_cmd) # | lolcat
|
|
95
|
+
# print("after")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def character_color(logo: str):
|
|
99
|
+
assert platform.system() == 'Windows', 'This function is only for Windows.'
|
|
100
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
101
|
+
f.write(ArtLib.cowsay(logo))
|
|
102
|
+
_new_art = f.name
|
|
103
|
+
os.system(f'type {_new_art} | lolcatjs') # | lolcat
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def character_or_box_color(logo: str):
|
|
107
|
+
assert platform.system() in {'Linux', 'Darwin'}, 'This function is only for Linux and macOS.'
|
|
108
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
109
|
+
_new_art = f.name
|
|
110
|
+
get_art(logo, artlib=None, file=_new_art, verbose=False)
|
|
111
|
+
# Prefer bat on mac if available, fallback to cat
|
|
112
|
+
pager = "bat" if (platform.system() == "Darwin" and any((Path(p).joinpath("bat").exists() for p in os.environ.get("PATH", "").split(os.pathsep)))) else "cat"
|
|
113
|
+
command = f"{pager} {_new_art} | lolcat"
|
|
114
|
+
os.system(command)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == '__main__':
|
|
118
|
+
pass
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
import glob
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import random
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from rich import pretty
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_header():
|
|
14
|
+
console = Console()
|
|
15
|
+
pretty.install()
|
|
16
|
+
_header = f"🐍 Python {platform.python_version()} in VE `{os.getenv('VIRTUAL_ENV')}` On {platform.system()} 🐍"
|
|
17
|
+
_header = Text(_header)
|
|
18
|
+
_header.stylize("bold blue")
|
|
19
|
+
console.rule(_header, style="bold red", align="center")
|
|
20
|
+
version = "14.5"
|
|
21
|
+
_ = Text(f"✨ 🐊 Crocodile Shell {version} ✨" + f" Made with 🐍 | Built with ❤️. Running @ {Path.cwd()}")
|
|
22
|
+
_.stylize("#05f8fc on #293536")
|
|
23
|
+
console.print(_)
|
|
24
|
+
def print_logo(logo: str):
|
|
25
|
+
from machineconfig.utils.files.ascii_art import font_box_color, character_color, character_or_box_color
|
|
26
|
+
if platform.system() == "Windows":
|
|
27
|
+
_1x = Path.home().joinpath(r"AppData/Roaming/npm/figlet").exists()
|
|
28
|
+
_2x = Path.home().joinpath(r"AppData/Roaming/npm/lolcatjs").exists()
|
|
29
|
+
_3x = Path.home().joinpath(r"AppData/Local/Microsoft/WindowsApps/boxes.exe").exists()
|
|
30
|
+
if _1x and _2x and _3x:
|
|
31
|
+
if random.choice([True, True, False]): font_box_color(logo)
|
|
32
|
+
else: character_color(logo)
|
|
33
|
+
else:
|
|
34
|
+
print("\n" + "🚫 " + "-" * 70 + " 🚫")
|
|
35
|
+
print("🔍 Missing ASCII art dependencies. Install with: iwr bit.ly/cfgasciiartwindows | iex")
|
|
36
|
+
print("🚫 " + "-" * 70 + " 🚫\n")
|
|
37
|
+
_default_art = Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*")))))
|
|
38
|
+
print(_default_art.read_text())
|
|
39
|
+
elif platform.system() in ["Linux", "Darwin"]: # Explicitly handle both Linux and macOS
|
|
40
|
+
def is_executable_in_path(executable_name: str) -> Optional[str]:
|
|
41
|
+
path_dirs = os.environ['PATH'].split(os.pathsep)
|
|
42
|
+
for path_dir in path_dirs:
|
|
43
|
+
path_to_executable = os.path.join(path_dir, executable_name)
|
|
44
|
+
if os.path.isfile(path_to_executable) and os.access(path_to_executable, os.X_OK): return path_to_executable
|
|
45
|
+
return None
|
|
46
|
+
avail_cowsay = is_executable_in_path("cowsay")
|
|
47
|
+
avail_lolcat = is_executable_in_path("lolcat")
|
|
48
|
+
avail_boxes = is_executable_in_path("boxes")
|
|
49
|
+
avail_figlet = is_executable_in_path("figlet")
|
|
50
|
+
if avail_cowsay and avail_lolcat and avail_boxes and avail_figlet:
|
|
51
|
+
_dynamic_art = random.choice([True, True, True, True, False])
|
|
52
|
+
if _dynamic_art: character_or_box_color(logo=logo)
|
|
53
|
+
else: print(Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*"))))).read_text())
|
|
54
|
+
else:
|
|
55
|
+
print("\n" + "🚫 " + "-" * 70 + " 🚫")
|
|
56
|
+
install_cmd = "devops install --group TerminalEyeCandy" if platform.system() == "Linux" else "brew install cowsay lolcat boxes figlet"
|
|
57
|
+
print(f"🔍 Missing ASCII art dependencies. Install with: {install_cmd}")
|
|
58
|
+
print("🚫 " + "-" * 70 + " 🚫\n")
|
|
59
|
+
_default_art = Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*")))))
|
|
60
|
+
print(_default_art.read_text())
|
|
61
|
+
else:
|
|
62
|
+
print(f"⚠️ Platform {platform.system()} not supported for ASCII art. Using default art.")
|
|
63
|
+
_default_art = Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*")))))
|
|
64
|
+
print(_default_art.read_text())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Read:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def read(path: 'Path', **kwargs: Any) -> Any:
|
|
11
|
+
if Path(path).is_dir(): raise IsADirectoryError(f"Path is a directory, not a file: {path}")
|
|
12
|
+
suffix = Path(path).suffix[1:]
|
|
13
|
+
if suffix == "": raise ValueError(f"File type could not be inferred from suffix. Suffix is empty. Path: {path}")
|
|
14
|
+
if suffix in ("sqlite", "sqlite3", "db", "duckdb"):
|
|
15
|
+
# from crocodile.database import DBMS
|
|
16
|
+
# if suffix == "duckdb": pass
|
|
17
|
+
# res = DBMS.from_local_db(path=path)
|
|
18
|
+
# print(res.describe_db())
|
|
19
|
+
# return res
|
|
20
|
+
raise NotImplementedError("Reading database files is not implemented yet. Use `crocodile.database.DBMS` to connect to the database file.")
|
|
21
|
+
try: return getattr(Read, suffix)(str(path), **kwargs)
|
|
22
|
+
except AttributeError as err:
|
|
23
|
+
if "type object 'Read' has no attribute" not in str(err): raise AttributeError(err) from err
|
|
24
|
+
if suffix in ('eps', 'jpg', 'jpeg', 'pdf', 'pgf', 'png', 'ps', 'raw', 'rgba', 'svg', 'svgz', 'tif', 'tiff'):
|
|
25
|
+
import matplotlib.pyplot as pyplot
|
|
26
|
+
return pyplot.imread(path, **kwargs) # from: plt.gcf().canvas.get_supported_filetypes().keys():
|
|
27
|
+
if suffix == "parquet":
|
|
28
|
+
import polars as pl
|
|
29
|
+
return pl.read_parquet(path, **kwargs)
|
|
30
|
+
elif suffix == "csv":
|
|
31
|
+
import polars as pl
|
|
32
|
+
return pl.read_csv(path, **kwargs)
|
|
33
|
+
try:
|
|
34
|
+
# guess = install_n_import('magic', 'python-magic').from_file(path)
|
|
35
|
+
guess = "IDKm"
|
|
36
|
+
raise AttributeError(f"Unknown file type. failed to recognize the suffix `{suffix}`. According to libmagic1, the file seems to be: {guess}") from err
|
|
37
|
+
except ImportError as err2:
|
|
38
|
+
print(f"💥 Unknown file type. failed to recognize the suffix `{suffix}` of file {path} ")
|
|
39
|
+
raise ImportError(err) from err2
|
|
40
|
+
@staticmethod
|
|
41
|
+
def json(path: 'Path', r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
|
|
42
|
+
import json
|
|
43
|
+
try:
|
|
44
|
+
mydict = json.loads(Path(path).read_text(encoding='utf-8'), **kwargs)
|
|
45
|
+
except Exception:
|
|
46
|
+
import pyjson5
|
|
47
|
+
mydict = pyjson5.loads(Path(path).read_text(encoding='utf-8'), **kwargs) # file has C-style comments.
|
|
48
|
+
_ = r
|
|
49
|
+
return mydict
|
|
50
|
+
@staticmethod
|
|
51
|
+
def yaml(path: 'Path', r: bool = False) -> Any: # return could be list or dict etc
|
|
52
|
+
import yaml # type: ignore
|
|
53
|
+
with open(str(path), "r", encoding="utf-8") as file:
|
|
54
|
+
mydict = yaml.load(file, Loader=yaml.FullLoader)
|
|
55
|
+
_ = r
|
|
56
|
+
return mydict
|
|
57
|
+
@staticmethod
|
|
58
|
+
def ini(path: 'Path', encoding: Optional[str] = None):
|
|
59
|
+
if not Path(path).exists() or Path(path).is_dir(): raise FileNotFoundError(f"File not found or is a directory: {path}")
|
|
60
|
+
import configparser
|
|
61
|
+
res = configparser.ConfigParser()
|
|
62
|
+
res.read(filenames=[str(path)], encoding=encoding)
|
|
63
|
+
return res
|
|
64
|
+
@staticmethod
|
|
65
|
+
def toml(path: 'Path'):
|
|
66
|
+
import toml
|
|
67
|
+
return toml.loads(Path(path).read_text(encoding='utf-8'))
|
|
68
|
+
@staticmethod
|
|
69
|
+
def npy(path: 'Path', **kwargs: Any):
|
|
70
|
+
import numpy as np
|
|
71
|
+
data = np.load(str(path), allow_pickle=True, **kwargs)
|
|
72
|
+
# data = data.item() if data.dtype == np.object else data
|
|
73
|
+
return data
|
|
74
|
+
@staticmethod
|
|
75
|
+
def pickle(path: 'Path', **kwargs: Any):
|
|
76
|
+
import pickle
|
|
77
|
+
try: return pickle.loads(Path(path).read_bytes(), **kwargs)
|
|
78
|
+
except BaseException as ex:
|
|
79
|
+
print(f"💥 Failed to load pickle file `{path}` with error:\n{ex}")
|
|
80
|
+
raise ex
|
|
81
|
+
@staticmethod
|
|
82
|
+
def pkl(path: 'Path', **kwargs: Any): return Read.pickle(path, **kwargs)
|
|
83
|
+
# @staticmethod
|
|
84
|
+
# def dill(path: 'Path', **kwargs: Any) -> Any:
|
|
85
|
+
# """handles imports automatically provided that saved object was from an imported class (not in defined in __main__)"""
|
|
86
|
+
# import dill
|
|
87
|
+
# obj = dill.loads(str=Path(path).read_bytes(), **kwargs)
|
|
88
|
+
# return obj
|
|
89
|
+
@staticmethod
|
|
90
|
+
def py(path: 'Path', init_globals: Optional[dict[str, Any]] = None, run_name: Optional[str] = None):
|
|
91
|
+
import runpy
|
|
92
|
+
return runpy.run_path(str(path), init_globals=init_globals, run_name=run_name)
|
|
93
|
+
@staticmethod
|
|
94
|
+
def txt(path: 'Path', encoding: str = 'utf-8') -> str: return Path(path).read_text(encoding=encoding)
|
|
95
|
+
@staticmethod
|
|
96
|
+
def parquet(path: 'Path', **kwargs: Any):
|
|
97
|
+
import polars as pl
|
|
98
|
+
return pl.read_parquet(path, **kwargs)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == '__main__':
|
|
103
|
+
pass
|
|
@@ -124,6 +124,13 @@ def check_tool_exists(tool_name: str) -> bool:
|
|
|
124
124
|
return any([Path("/usr/local/bin").joinpath(tool_name).is_file(), Path("/usr/bin").joinpath(tool_name).is_file(), root_path.joinpath(tool_name).is_file()])
|
|
125
125
|
else:
|
|
126
126
|
raise NotImplementedError(f"platform {platform.system()} not implemented")
|
|
127
|
+
def is_executable_in_path(executable_name: str) -> bool:
|
|
128
|
+
import os
|
|
129
|
+
path_dirs = os.environ['PATH'].split(os.pathsep)
|
|
130
|
+
for path_dir in path_dirs:
|
|
131
|
+
path_to_executable = os.path.join(path_dir, executable_name)
|
|
132
|
+
if os.path.isfile(path_to_executable) and os.access(path_to_executable, os.X_OK): return True
|
|
133
|
+
return False
|
|
127
134
|
|
|
128
135
|
|
|
129
136
|
def check_if_installed_already(exe_name: str, version: Optional[str], use_cache: bool) -> tuple[str, str, str]:
|
machineconfig/utils/links.py
CHANGED
|
@@ -2,11 +2,62 @@ from machineconfig.utils.path_extended import PathExtended, PLike
|
|
|
2
2
|
from machineconfig.utils.accessories import randstr
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
from rich.panel import Panel
|
|
5
|
-
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import TypedDict, Literal
|
|
6
7
|
|
|
7
8
|
console = Console()
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
class SymlinkResult(TypedDict):
|
|
12
|
+
action: Literal[
|
|
13
|
+
"already_linked",
|
|
14
|
+
"relinking",
|
|
15
|
+
"fixing_broken_link",
|
|
16
|
+
"identical_files",
|
|
17
|
+
"backing_up_source",
|
|
18
|
+
"backing_up_target",
|
|
19
|
+
"relinking_to_new_target",
|
|
20
|
+
"moving_to_target",
|
|
21
|
+
"new_link",
|
|
22
|
+
"new_link_and_target",
|
|
23
|
+
"linking",
|
|
24
|
+
"error"
|
|
25
|
+
]
|
|
26
|
+
details: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CopyResult(TypedDict):
|
|
30
|
+
action: Literal[
|
|
31
|
+
"already_linked",
|
|
32
|
+
"relinking",
|
|
33
|
+
"fixing_broken_link",
|
|
34
|
+
"backing_up_source",
|
|
35
|
+
"backing_up_target",
|
|
36
|
+
"relinking_to_new_target",
|
|
37
|
+
"moving_to_target",
|
|
38
|
+
"new_link",
|
|
39
|
+
"new_link_and_target",
|
|
40
|
+
"copying",
|
|
41
|
+
"error"
|
|
42
|
+
]
|
|
43
|
+
details: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def files_are_identical(file1: PathExtended, file2: PathExtended) -> bool:
|
|
47
|
+
"""Check if two files are identical by comparing their SHA256 hashes."""
|
|
48
|
+
def get_file_hash(path: PathExtended) -> str:
|
|
49
|
+
hash_sha256 = hashlib.sha256()
|
|
50
|
+
with open(path, "rb") as f:
|
|
51
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
|
52
|
+
hash_sha256.update(chunk)
|
|
53
|
+
return hash_sha256.hexdigest()
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
return get_file_hash(file1) == get_file_hash(file2)
|
|
57
|
+
except (OSError, IOError):
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
10
61
|
def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
|
|
11
62
|
"""Build symboic links from various relevant paths (e.g. data) to `repo_root/links/<name>` to facilitate easy access from
|
|
12
63
|
tree explorer of the IDE.
|
|
@@ -33,12 +84,14 @@ def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
|
|
|
33
84
|
links_path.symlink_to(target=a_target_path)
|
|
34
85
|
|
|
35
86
|
|
|
36
|
-
def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool):
|
|
87
|
+
def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool) -> SymlinkResult:
|
|
37
88
|
"""helper function. creates a symlink from `this` to `to_this`.
|
|
38
89
|
|
|
90
|
+
Returns a dict with 'action' and 'details' keys describing what was done.
|
|
91
|
+
|
|
39
92
|
this: exists AND to_this exists AND this is a symlink pointing to to_this ===> Resolution: AUTO: do nothing, already linked correctly.
|
|
40
93
|
this: exists AND to_this exists AND this is a symlink pointing to somewhere else ===> Resolution: AUTO: delete this symlink, create symlink to to_this
|
|
41
|
-
this: exists AND to_this exists AND this is a concrete path ===> Resolution: DANGER:
|
|
94
|
+
this: exists AND to_this exists AND this is a concrete path ===> Resolution: DANGER: If files are identical (same hash), delete `this` and create symlink to `to_this`. Otherwise, two options: 1) prioritize `this`: to_this is backed up as to_this.orig_<randstr()>, to_this is deleted, and symlink is created from this to to_this as normal; 2) prioritize `to_this`: `this` is backed up as this.orig_<randstr()>, `this` is deleted, and symlink is created from this to to_this as normal.
|
|
42
95
|
|
|
43
96
|
this: exists AND to_this doesn't exist AND this is a symlink pointing to somewhere else ===> Resolution: AUTO: delete this symlink, create symlink to to_this (touch to_this)
|
|
44
97
|
this: exists AND to_this doesn't exist AND this is a symlink pointing to to_this ===> Resolution: AUTO: delete this symlink, create symlink to to_this (touch to_this)
|
|
@@ -50,6 +103,9 @@ def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this:
|
|
|
50
103
|
"""
|
|
51
104
|
this = PathExtended(this).expanduser().absolute()
|
|
52
105
|
to_this = PathExtended(to_this).expanduser().absolute()
|
|
106
|
+
action_taken = ""
|
|
107
|
+
details = ""
|
|
108
|
+
|
|
53
109
|
# Case analysis based on docstring
|
|
54
110
|
if this.exists():
|
|
55
111
|
if to_this.exists():
|
|
@@ -58,33 +114,53 @@ def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this:
|
|
|
58
114
|
try:
|
|
59
115
|
if this.readlink().resolve() == to_this.resolve():
|
|
60
116
|
# Case: this exists AND to_this exists AND this is a symlink pointing to to_this
|
|
117
|
+
action_taken = "already_linked"
|
|
118
|
+
details = "Symlink already correctly points to target"
|
|
61
119
|
console.print(Panel(f"✅ ALREADY LINKED | {this} ➡️ {to_this}", title="Already Linked", expand=False))
|
|
62
|
-
return
|
|
120
|
+
return {"action": action_taken, "details": details}
|
|
63
121
|
else:
|
|
64
122
|
# Case: this exists AND to_this exists AND this is a symlink pointing to somewhere else
|
|
123
|
+
action_taken = "relinking"
|
|
124
|
+
details = "Updated existing symlink to point to new target"
|
|
65
125
|
console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
|
|
66
126
|
this.delete(sure=True)
|
|
67
127
|
except OSError:
|
|
68
128
|
# Broken symlink case
|
|
129
|
+
action_taken = "fixing_broken_link"
|
|
130
|
+
details = "Removed broken symlink and will create new one"
|
|
69
131
|
console.print(Panel(f"🔄 FIXING BROKEN LINK | Fixing broken symlink from {this} ➡️ {to_this}", title="Fixing Broken Link", expand=False))
|
|
70
132
|
this.delete(sure=True)
|
|
71
133
|
else:
|
|
72
134
|
# Case: this exists AND to_this exists AND this is a concrete path
|
|
73
|
-
if
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
135
|
+
if files_are_identical(this, to_this):
|
|
136
|
+
# Files are identical, just delete this and create symlink
|
|
137
|
+
action_taken = "identical_files"
|
|
138
|
+
details = "Files identical, removed source and will create symlink"
|
|
139
|
+
console.print(Panel(f"🔗 IDENTICAL FILES | Files are identical, deleting {this} and creating symlink to {to_this}", title="Identical Files", expand=False))
|
|
140
|
+
this.delete(sure=True)
|
|
78
141
|
else:
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
142
|
+
# Files are different, use prioritization logic
|
|
143
|
+
if prioritize_to_this:
|
|
144
|
+
# prioritize `to_this`: `this` is backed up, `this` is deleted, symlink created
|
|
145
|
+
backup_name = f"{this}.orig_{randstr()}"
|
|
146
|
+
action_taken = "backing_up_source"
|
|
147
|
+
details = f"Backed up source to {backup_name}, prioritizing target"
|
|
148
|
+
console.print(Panel(f"📦 BACKING UP | Moving {this} to {backup_name}, prioritizing {to_this}", title="Backing Up", expand=False))
|
|
149
|
+
this.move(path=backup_name)
|
|
150
|
+
else:
|
|
151
|
+
# prioritize `this`: to_this is backed up, to_this is deleted, this content moved to to_this location
|
|
152
|
+
backup_name = f"{to_this}.orig_{randstr()}"
|
|
153
|
+
action_taken = "backing_up_target"
|
|
154
|
+
details = f"Backed up target to {backup_name}, prioritizing source"
|
|
155
|
+
console.print(Panel(f"📦 BACKING UP | Moving {to_this} to {backup_name}, prioritizing {this}", title="Backing Up", expand=False))
|
|
156
|
+
to_this.move(path=backup_name)
|
|
157
|
+
this.move(path=to_this)
|
|
84
158
|
else:
|
|
85
159
|
# to_this doesn't exist
|
|
86
160
|
if this.is_symlink():
|
|
87
161
|
# Case: this exists AND to_this doesn't exist AND this is a symlink (pointing anywhere)
|
|
162
|
+
action_taken = "relinking_to_new_target"
|
|
163
|
+
details = "Removed existing symlink, will create target and new symlink"
|
|
88
164
|
console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
|
|
89
165
|
this.delete(sure=True)
|
|
90
166
|
# Create to_this
|
|
@@ -92,70 +168,111 @@ def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this:
|
|
|
92
168
|
to_this.touch()
|
|
93
169
|
else:
|
|
94
170
|
# Case: this exists AND to_this doesn't exist AND this is a concrete path
|
|
171
|
+
action_taken = "moving_to_target"
|
|
172
|
+
details = "Moved source to target location, will create symlink"
|
|
95
173
|
console.print(Panel(f"📁 MOVING | Moving {this} to {to_this}, then creating symlink", title="Moving", expand=False))
|
|
96
174
|
this.move(path=to_this)
|
|
97
175
|
else:
|
|
98
176
|
# this doesn't exist
|
|
99
177
|
if to_this.exists():
|
|
100
178
|
# Case: this doesn't exist AND to_this exists
|
|
179
|
+
action_taken = "new_link"
|
|
180
|
+
details = "Creating new symlink to existing target"
|
|
101
181
|
console.print(Panel(f"🆕 NEW LINK | Creating new symlink from {this} ➡️ {to_this}", title="New Link", expand=False))
|
|
102
182
|
else:
|
|
103
183
|
# Case: this doesn't exist AND to_this doesn't exist
|
|
184
|
+
action_taken = "new_link_and_target"
|
|
185
|
+
details = "Creating target file and new symlink"
|
|
104
186
|
console.print(Panel(f"🆕 NEW LINK & TARGET | Creating {to_this} and symlink from {this} ➡️ {to_this}", title="New Link & Target", expand=False))
|
|
105
187
|
to_this.parent.mkdir(parents=True, exist_ok=True)
|
|
106
188
|
to_this.touch()
|
|
189
|
+
|
|
107
190
|
# Create the symlink
|
|
108
191
|
try:
|
|
192
|
+
action_taken = action_taken or "linking"
|
|
193
|
+
details = details or "Creating symlink"
|
|
109
194
|
console.print(Panel(f"🔗 LINKING | Creating symlink from {this} ➡️ {to_this}", title="Linking", expand=False))
|
|
110
195
|
PathExtended(this).symlink_to(target=to_this, verbose=True, overwrite=True)
|
|
196
|
+
return {"action": action_taken, "details": details}
|
|
111
197
|
except Exception as ex:
|
|
198
|
+
action_taken = "error"
|
|
199
|
+
details = f"Failed to create symlink: {str(ex)}"
|
|
112
200
|
console.print(Panel(f"❌ ERROR | Failed at linking {this} ➡️ {to_this}. Reason: {ex}", title="Error", expand=False))
|
|
201
|
+
return {"action": action_taken, "details": details}
|
|
113
202
|
|
|
114
203
|
|
|
115
|
-
def symlink_copy(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool):
|
|
204
|
+
def symlink_copy(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool) -> CopyResult:
|
|
116
205
|
this = PathExtended(this).expanduser().absolute()
|
|
117
206
|
to_this = PathExtended(to_this).expanduser().absolute()
|
|
207
|
+
action_taken = ""
|
|
208
|
+
details = ""
|
|
209
|
+
|
|
118
210
|
if this.exists():
|
|
119
211
|
if to_this.exists():
|
|
120
212
|
if this.is_symlink():
|
|
121
213
|
try:
|
|
122
214
|
if this.readlink().resolve() == to_this.resolve():
|
|
215
|
+
action_taken = "already_linked"
|
|
216
|
+
details = "Symlink already correctly points to target"
|
|
123
217
|
console.print(Panel(f"✅ ALREADY LINKED | {this} ➡️ {to_this}", title="Already Linked", expand=False))
|
|
124
|
-
return
|
|
218
|
+
return {"action": action_taken, "details": details}
|
|
125
219
|
else:
|
|
220
|
+
action_taken = "relinking"
|
|
221
|
+
details = "Updated existing symlink to point to new target"
|
|
126
222
|
console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
|
|
127
223
|
this.delete(sure=True)
|
|
128
224
|
except OSError:
|
|
225
|
+
action_taken = "fixing_broken_link"
|
|
226
|
+
details = "Removed broken symlink and will create new one"
|
|
129
227
|
console.print(Panel(f"🔄 FIXING BROKEN LINK | Fixing broken symlink from {this} ➡️ {to_this}", title="Fixing Broken Link", expand=False))
|
|
130
228
|
this.delete(sure=True)
|
|
131
229
|
else:
|
|
132
230
|
if prioritize_to_this:
|
|
133
231
|
backup_name = f"{this}.orig_{randstr()}"
|
|
232
|
+
action_taken = "backing_up_source"
|
|
233
|
+
details = f"Backed up source to {backup_name}, prioritizing target"
|
|
134
234
|
console.print(Panel(f"📦 BACKING UP | Moving {this} to {backup_name}, prioritizing {to_this}", title="Backing Up", expand=False))
|
|
135
235
|
this.move(path=backup_name)
|
|
136
236
|
else:
|
|
137
237
|
backup_name = f"{to_this}.orig_{randstr()}"
|
|
238
|
+
action_taken = "backing_up_target"
|
|
239
|
+
details = f"Backed up target to {backup_name}, prioritizing source"
|
|
138
240
|
console.print(Panel(f"📦 BACKING UP | Moving {to_this} to {backup_name}, prioritizing {this}", title="Backing Up", expand=False))
|
|
139
241
|
to_this.move(path=backup_name)
|
|
140
242
|
this.move(path=to_this)
|
|
141
243
|
else:
|
|
142
244
|
if this.is_symlink():
|
|
245
|
+
action_taken = "relinking_to_new_target"
|
|
246
|
+
details = "Removed existing symlink, will create target and new symlink"
|
|
143
247
|
console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
|
|
144
248
|
this.delete(sure=True)
|
|
145
249
|
to_this.parent.mkdir(parents=True, exist_ok=True)
|
|
146
250
|
to_this.touch()
|
|
147
251
|
else:
|
|
252
|
+
action_taken = "moving_to_target"
|
|
253
|
+
details = "Moved source to target location, will copy"
|
|
148
254
|
console.print(Panel(f"📁 MOVING | Moving {this} to {to_this}, then copying", title="Moving", expand=False))
|
|
149
255
|
this.move(path=to_this)
|
|
150
256
|
else:
|
|
151
257
|
if to_this.exists():
|
|
258
|
+
action_taken = "new_link"
|
|
259
|
+
details = "Copying existing target to source location"
|
|
152
260
|
console.print(Panel(f"🆕 NEW LINK | Copying {to_this} to {this}", title="New Link", expand=False))
|
|
153
261
|
else:
|
|
262
|
+
action_taken = "new_link_and_target"
|
|
263
|
+
details = "Creating target file and copying to source"
|
|
154
264
|
console.print(Panel(f"🆕 NEW LINK & TARGET | Creating {to_this} and copying to {this}", title="New Link & Target", expand=False))
|
|
155
265
|
to_this.parent.mkdir(parents=True, exist_ok=True)
|
|
156
266
|
to_this.touch()
|
|
267
|
+
|
|
157
268
|
try:
|
|
269
|
+
action_taken = action_taken or "copying"
|
|
270
|
+
details = details or "Copying file"
|
|
158
271
|
console.print(Panel(f"📋 COPYING | Copying {to_this} to {this}", title="Copying", expand=False))
|
|
159
272
|
to_this.copy(path=this, overwrite=True, verbose=True)
|
|
273
|
+
return {"action": action_taken, "details": details}
|
|
160
274
|
except Exception as ex:
|
|
275
|
+
action_taken = "error"
|
|
276
|
+
details = f"Failed to copy file: {str(ex)}"
|
|
161
277
|
console.print(Panel(f"❌ ERROR | Failed at copying {to_this} to {this}. Reason: {ex}", title="Error", expand=False))
|
|
278
|
+
return {"action": action_taken, "details": details}
|
machineconfig/utils/options.py
CHANGED
|
@@ -6,31 +6,6 @@ from rich.console import Console
|
|
|
6
6
|
import subprocess
|
|
7
7
|
from typing import Optional, Union, Iterable, overload, Literal
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
# _ = cmd
|
|
11
|
-
# cmd = "where.exe"
|
|
12
|
-
# cmd = "which"
|
|
13
|
-
# try: # talking to terminal is too slow.
|
|
14
|
-
# _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
|
|
15
|
-
# res: bool = True
|
|
16
|
-
# except (subprocess.CalledProcessError, FileNotFoundError):
|
|
17
|
-
# res = False
|
|
18
|
-
# return res
|
|
19
|
-
# return root_path.joinpath(tool_name).is_file()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# def choose_from_options[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
|
|
23
|
-
# choice_key = choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=False, custom_input=custom_input)
|
|
24
|
-
# assert not isinstance(choice_key, list)
|
|
25
|
-
# return choice_key
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# def choose_from_options[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, custom_input: bool = False) -> list[T]:
|
|
29
|
-
# choice_key = choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=True, multi=True, custom_input=custom_input)
|
|
30
|
-
# if isinstance(choice_key, list):
|
|
31
|
-
# return choice_key
|
|
32
|
-
# return [choice_key]
|
|
33
|
-
|
|
34
9
|
@overload
|
|
35
10
|
def choose_from_options[T](msg: str, options: Iterable[T], multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False) -> T: ...
|
|
36
11
|
@overload
|
|
@@ -78,11 +78,8 @@ machineconfig/jobs/linux/msc/cli_agents.sh,sha256=MMa_cd4yijI69c7tztTY1b0tl9I1EC
|
|
|
78
78
|
machineconfig/jobs/linux/msc/lid.sh,sha256=09LeoSaXCGjCn7YxPcIFQpHroYdglJlEtFU2agarh3I,1302
|
|
79
79
|
machineconfig/jobs/linux/msc/network.sh,sha256=dmISsh0hioDheinqee3qHfo2k7ClFx6G_GfGDxuflmc,1796
|
|
80
80
|
machineconfig/jobs/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
|
-
machineconfig/jobs/python/check_installations.py,sha256=
|
|
82
|
-
machineconfig/jobs/python/create_bootable_media.py,sha256=KKtcPk0rFLQc4eNVP6nbeYX-P7Gpqi0HvfIcUM6rVVs,827
|
|
83
|
-
machineconfig/jobs/python/python_cargo_build_share.py,sha256=vy1v32-7Tui4NK4wG5XC5hxavQ4BeMpKprUtqzBjut0,2081
|
|
81
|
+
machineconfig/jobs/python/check_installations.py,sha256=wOtvWzyJSxbuFueFfcOc4gX_UbTRWv6tWpRcG-3Ml_8,10780
|
|
84
82
|
machineconfig/jobs/python/python_ve_symlink.py,sha256=Mw2SK_TDLK5Ct_mEESh_Pd-Rn-B1oBSp7a_9y_eZbqw,1140
|
|
85
|
-
machineconfig/jobs/python/tasks.py,sha256=hrBDQOnBmcXtauTkicVgC8J2AOGcfdFfyx0K8eI6Coc,150
|
|
86
83
|
machineconfig/jobs/python/vscode/api.py,sha256=Et0G-VUj13D1rshYMdDrw_CUYSO7Q6XRrEQO0WjVIKU,1683
|
|
87
84
|
machineconfig/jobs/python/vscode/sync_code.py,sha256=f9hxMg_nkIsC0xvfQMboJbc-Jhap9YQrV7k7a5YSI1c,2333
|
|
88
85
|
machineconfig/jobs/windows/start_terminal.ps1,sha256=wy0fGwgb4U7xaHsONDrR4V5u9JEkG5vtt4NZUBx0ro8,473
|
|
@@ -93,7 +90,7 @@ machineconfig/jobs/windows/archive/openssh-server_copy-ssh-id.ps1,sha256=-7pElYi
|
|
|
93
90
|
machineconfig/jobs/windows/msc/cli_agents.bat,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
91
|
machineconfig/jobs/windows/msc/cli_agents.ps1,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
92
|
machineconfig/profile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
96
|
-
machineconfig/profile/create.py,sha256
|
|
93
|
+
machineconfig/profile/create.py,sha256=QtPOj0aXLTd7XoAu-jBOQyZ_UM0Axc-OC-LCPMZ1Q7U,12390
|
|
97
94
|
machineconfig/profile/shell.py,sha256=eAAmYoROXX1V3vk9-jcRSnv03P2Wx3_N4UgFtUDLtKU,9199
|
|
98
95
|
machineconfig/profile/records/generic/shares.toml,sha256=FduDztfyQtZcr5bfx-RSKhEEweweQSWfVXkKWnx8hCY,143
|
|
99
96
|
machineconfig/profile/records/linux/apps_summary_report.csv,sha256=pw9djvaRUPalKDLn2sl3odcbD2_Zx3aEupsQ8UPfaaY,2738
|
|
@@ -145,9 +142,9 @@ machineconfig/scripts/python/cloud_manager.py,sha256=YN0DYLzPKtMBaks-EAVwFmkCu3X
|
|
|
145
142
|
machineconfig/scripts/python/cloud_mount.py,sha256=GwcXbd5ohoHGESfX5edtCEl2-umDDxH_AZapmFSzc9E,6740
|
|
146
143
|
machineconfig/scripts/python/cloud_repo_sync.py,sha256=8dnlHbQqRymPRU0v01pNIuaIvFeY4fReP7ewNSSCt34,9765
|
|
147
144
|
machineconfig/scripts/python/cloud_sync.py,sha256=RWGpAfJ9fnN18yNBSgN44dzA38Hmd4879JL5r2pcyrM,3514
|
|
148
|
-
machineconfig/scripts/python/count_lines.py,sha256=
|
|
149
|
-
machineconfig/scripts/python/count_lines_frontend.py,sha256=
|
|
150
|
-
machineconfig/scripts/python/croshell.py,sha256=
|
|
145
|
+
machineconfig/scripts/python/count_lines.py,sha256=ZexMRsV70pe9fhLbGuens9EP5gCf078EwTDRHRZo5A0,15960
|
|
146
|
+
machineconfig/scripts/python/count_lines_frontend.py,sha256=tqUiJggHC8_uy5ZV81i_ycxM1x0j-2nJin3e3FJqLXs,521
|
|
147
|
+
machineconfig/scripts/python/croshell.py,sha256=ggLy1X45Dqo8AV8qUY2-O74r7yQNc9nnRhzt8RisMX8,6535
|
|
151
148
|
machineconfig/scripts/python/devops.py,sha256=JB4_M6S-nO3yqas8wtAlU2r6jsmHu_nlq7aoEOH-54Y,3486
|
|
152
149
|
machineconfig/scripts/python/devops_add_identity.py,sha256=wvjNgqsLmqD2SxbNCW_usqfp0LI-TDvcJJKGOWt2oFw,3775
|
|
153
150
|
machineconfig/scripts/python/devops_add_ssh_key.py,sha256=BXB-9RvuSZO0YTbnM2azeABW2ngLW4SKhhAGAieMzfw,6873
|
|
@@ -165,13 +162,13 @@ machineconfig/scripts/python/fire_jobs_streamlit_helper.py,sha256=47DEQpj8HBSa-_
|
|
|
165
162
|
machineconfig/scripts/python/ftpx.py,sha256=QfQTp-6jQP6yxfbLc5sKxiMtTgAgc8sjN7d17_uLiZc,9400
|
|
166
163
|
machineconfig/scripts/python/get_zellij_cmd.py,sha256=e35-18hoXM9N3PFbvbizfkNY_-63iMicieWE3TbGcCQ,576
|
|
167
164
|
machineconfig/scripts/python/gh_models.py,sha256=3BLfW25mBRiPO5VKtVm-nMlKLv-PaZDw7mObajq6F6M,5538
|
|
168
|
-
machineconfig/scripts/python/interactive.py,sha256=
|
|
165
|
+
machineconfig/scripts/python/interactive.py,sha256=wjxwxU5KtCh8MgujCQjEQctZPpKfPc71lMVFLhODQFE,11769
|
|
169
166
|
machineconfig/scripts/python/mount_nfs.py,sha256=aECrL64j9g-9rF49sVJAjGmzaoGgcMnl3g9v17kQF4c,3239
|
|
170
167
|
machineconfig/scripts/python/mount_nw_drive.py,sha256=iru6AtnTyvyuk6WxlK5R4lDkuliVpPV5_uBTVVhXtjQ,1550
|
|
171
168
|
machineconfig/scripts/python/mount_ssh.py,sha256=k2fKq3f5dKq_7anrFOlqvJoI_3U4EWNHLRZ1o3Lsy6M,2268
|
|
172
169
|
machineconfig/scripts/python/onetimeshare.py,sha256=bmGsNnskym5OWfIhpOfZG5jq3m89FS0a6dF5Sb8LaZM,2539
|
|
173
170
|
machineconfig/scripts/python/pomodoro.py,sha256=SPkfeoZGv8rylGiOyzQ7UK3aXZ3G2FIOuGkSuBUggOI,2019
|
|
174
|
-
machineconfig/scripts/python/repos.py,sha256=
|
|
171
|
+
machineconfig/scripts/python/repos.py,sha256=IidAfUx6jFs4dB8Wjq8ems8mS8X8jYFgvEhtCYdLs-A,4917
|
|
175
172
|
machineconfig/scripts/python/repos_helper.py,sha256=3jLdnNf1canpzi3JXiz5VA6UTUmLeNHuhjOWVl_thP0,3006
|
|
176
173
|
machineconfig/scripts/python/repos_helper_action.py,sha256=sXeOw5uHaK2GJixYW8qU_PD24mruGcQ59uf68ELC76A,14846
|
|
177
174
|
machineconfig/scripts/python/repos_helper_clone.py,sha256=9vGb9NCXT0lkerPzOJjmFfhU8LSzE-_1LDvjkhgnal0,5461
|
|
@@ -382,9 +379,9 @@ machineconfig/utils/accessories.py,sha256=W_9dLzjwNTW5JQk_pe3B2ijQ1nA2-8Kdg2r7VB
|
|
|
382
379
|
machineconfig/utils/code.py,sha256=S7uY5kLPxLcLlR7B2KHeYkenlysAYSPcxFiUYHXSxX8,5646
|
|
383
380
|
machineconfig/utils/installer.py,sha256=xYM6tyctqLmr2lLXUKWgobTRufGIua31uspMXP4HGjY,9945
|
|
384
381
|
machineconfig/utils/io.py,sha256=ZXB3aataS1IZ_0WMcCRSmoN1nbkvEO-bWYcs-TpngqU,2872
|
|
385
|
-
machineconfig/utils/links.py,sha256=
|
|
382
|
+
machineconfig/utils/links.py,sha256=S0XICdbcFESUqm5RINDrOf3O8G1b7QEADncXXcC8IQc,15520
|
|
386
383
|
machineconfig/utils/notifications.py,sha256=vvdsY5IX6XEiILTnt5lNyHxhCi0ljdGX2T_67VRfrG4,9009
|
|
387
|
-
machineconfig/utils/options.py,sha256=
|
|
384
|
+
machineconfig/utils/options.py,sha256=vUO4Kej-vDOv64wHr2HNDyu6PATURpjd7xp6N8OOoJg,7083
|
|
388
385
|
machineconfig/utils/path_extended.py,sha256=Xjdn2AVnB8p1jfNMNe2kJutVa5zGnFFJVGZbw-Bp_hg,53200
|
|
389
386
|
machineconfig/utils/path_helper.py,sha256=0e3Xh3BAEv27oqcezNeVLHJllGmLEgLH4T1l90m-650,8014
|
|
390
387
|
machineconfig/utils/procs.py,sha256=Bm-yopmj19yiBO9tywJHEcs9rZmeRyJqbgTSe216LTU,11349
|
|
@@ -399,17 +396,20 @@ machineconfig/utils/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
399
396
|
machineconfig/utils/ai/generate_file_checklist.py,sha256=ajbmhcBToRugl75c_KZRq2XJumxKgIqQhyf7_YtF5q4,2729
|
|
400
397
|
machineconfig/utils/cloud/onedrive/setup_oauth.py,sha256=ZTVkqgrwbV_EoPvyT8dyOTUE0ur3BW4sa9o6QYtt5Bo,2341
|
|
401
398
|
machineconfig/utils/cloud/onedrive/transaction.py,sha256=m-aNcnWj_gfZVvJOSpkdIqjZxU_3nXx2CA-qKbQgP3I,26232
|
|
399
|
+
machineconfig/utils/files/ascii_art.py,sha256=cNJaJC07vx94fS44-tzgfbfBeCwXVrgpnWGBLUnfC38,5212
|
|
400
|
+
machineconfig/utils/files/headers.py,sha256=crnjWt5R0rmZOQ45KhF6HtFpsUm1oeCPoYeptR4o-U0,3437
|
|
401
|
+
machineconfig/utils/files/read.py,sha256=dyf-DDkLnCKgwmRH0Af4UtBIZh6iMUaYmjgJtdSGRFg,4744
|
|
402
402
|
machineconfig/utils/installer_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
403
403
|
machineconfig/utils/installer_utils/github_release_bulk.py,sha256=WJf_qZlF02SmIc6C7o1h4Gy4gAaJAfeAS8O9s2Itj-k,6535
|
|
404
404
|
machineconfig/utils/installer_utils/installer.py,sha256=_XcatwArhwRepMYfaGYpjd-lqNGfijnjeZB8l4uKd-c,9266
|
|
405
|
-
machineconfig/utils/installer_utils/installer_abc.py,sha256=
|
|
405
|
+
machineconfig/utils/installer_utils/installer_abc.py,sha256=VTHe5O3jA6k6rnUnXhgnEf6mVvkVQlEuXjzYLEDgEAs,11140
|
|
406
406
|
machineconfig/utils/installer_utils/installer_class.py,sha256=fN4Nfqn4tlSfQGW52A_Ipi6GT6utC30ZuSj5WM_yxIY,20252
|
|
407
407
|
machineconfig/utils/schemas/fire_agents/fire_agents_input.py,sha256=pTxvLzIpD5RF508lUUBBkWcc4V1B10J4ylvVgVGkcM0,2037
|
|
408
408
|
machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoSpdmTIdgS9LS-RvE-QZ-D260tD3o,1214
|
|
409
409
|
machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
|
|
410
410
|
machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
|
|
411
|
-
machineconfig-5.
|
|
412
|
-
machineconfig-5.
|
|
413
|
-
machineconfig-5.
|
|
414
|
-
machineconfig-5.
|
|
415
|
-
machineconfig-5.
|
|
411
|
+
machineconfig-5.14.dist-info/METADATA,sha256=mFhObJHO12yrWPAN5ujBFZSEscyXA61ekpyxdMlytj0,8030
|
|
412
|
+
machineconfig-5.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
413
|
+
machineconfig-5.14.dist-info/entry_points.txt,sha256=2afE1mw-o4MUlfxyX73SV02XaQI4SV_LdL2r6_CzhPU,1074
|
|
414
|
+
machineconfig-5.14.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
|
|
415
|
+
machineconfig-5.14.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# try ventory or netboot.xyz
|
|
2
|
-
|
|
3
|
-
# # one can either install rufus: https://rufus.ie/en/
|
|
4
|
-
# # however, to create bootable media with multiple OSs to choose from:
|
|
5
|
-
|
|
6
|
-
# PathExtended(r'https://github.com/ventoy/Ventoy/archive/refs/tags/v1.0.78.zip').download().unzip().search[0]()
|
|
7
|
-
# download_folder = PathExtended.home().joinpath("Downloads/os")
|
|
8
|
-
# download_folder.mkdir(parents=True, exist_ok=True)
|
|
9
|
-
# PathExtended(r'https://mirrors.layeronline.com/linuxmint/stable/21/linuxmint-21-cinnamon-64bit.iso').download(folder=download_folder)
|
|
10
|
-
# download_folder2 = PathExtended.home().joinpath("Downloads/os")
|
|
11
|
-
# download_folder2.mkdir(parents=True, exist_ok=True)
|
|
12
|
-
# PathExtended(r'https://download.manjaro.org/kde/21.3.7/manjaro-kde-21.3.7-minimal-220816-linux515.iso').download(folder=download_folder2)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# if __name__ == '__main__':
|
|
16
|
-
# pass
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
cargo install
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
# from machineconfig.utils.path_reduced import P as PathExtended
|
|
6
|
-
# import platform
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# def build_rust_executable(url: str=r"https://github.com/atanunq/viu"):
|
|
10
|
-
# tool_name = url.split('/')[-1]
|
|
11
|
-
|
|
12
|
-
# # move command is not required since tool will go to .cargo/bin which is in PATH by default.
|
|
13
|
-
# # move_command = f"mv {exe} {tb.get_env().WindowsApps.as_posix()}/" if platform.platform() == "Windows" else f"sudo mv {exe} /usr/local/bin/"
|
|
14
|
-
# # {move_command}
|
|
15
|
-
|
|
16
|
-
# script = f"""
|
|
17
|
-
# cd ~
|
|
18
|
-
# git clone --depth 1 {url}
|
|
19
|
-
# cd {tool_name}
|
|
20
|
-
# cargo install --path .
|
|
21
|
-
# """
|
|
22
|
-
# print(f"""
|
|
23
|
-
# {'=' * 150}
|
|
24
|
-
# 🦀 CARGO BUILD | Building Rust project: {tool_name}
|
|
25
|
-
# 📦 Source: {url}
|
|
26
|
-
# {'=' * 150}
|
|
27
|
-
# """)
|
|
28
|
-
# if platform.system() == "Windows":
|
|
29
|
-
# Terminal(stdout=None).run(f". {PathExtended.tmpfile(suffix='.ps1').write_text(script, encoding="utf-8")}", shell="pwsh").print()
|
|
30
|
-
# else:
|
|
31
|
-
# Terminal(stdout=None).run(script, shell="pwsh")
|
|
32
|
-
|
|
33
|
-
# exe = PathExtended.home().joinpath(f".cargo/bin/{tool_name}" + (".exe" if platform.system() == "Windows" else ""))
|
|
34
|
-
|
|
35
|
-
# try:
|
|
36
|
-
# PathExtended.home().joinpath(tool_name).delete(sure=True)
|
|
37
|
-
# except PermissionError:
|
|
38
|
-
# print(f"""
|
|
39
|
-
# {'⚠️' * 20}
|
|
40
|
-
# ⚠️ WARNING | Permission error when cleaning up
|
|
41
|
-
# 📂 Path: {PathExtended.home().joinpath(tool_name)}
|
|
42
|
-
# {'⚠️' * 20}
|
|
43
|
-
# """)
|
|
44
|
-
|
|
45
|
-
# if platform.system() == "Windows":
|
|
46
|
-
# exe = exe.move(folder=PathExtended.get_env().WindowsPaths().WindowsApps)
|
|
47
|
-
# elif platform.system() in ["Linux", "Darwin"]:
|
|
48
|
-
# Terminal().run(f"sudo mv {exe} /usr/local/bin")
|
|
49
|
-
# exe = PathExtended(r"/usr/local/bin").joinpath(exe.name)
|
|
50
|
-
# else:
|
|
51
|
-
# raise NotImplementedError(f"🚫 Platform {platform.system()} not supported.")
|
|
52
|
-
# share_link = exe.to_cloud("gdpo", share=True)
|
|
53
|
-
# return share_link
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# after cargo install diskonaut
|
|
57
|
-
# then mv ~/.cargo/bin/diskonaut.exe ~/AppData/Local/Microsoft/WindowsApps/
|
|
58
|
-
# then bu_gdrive_sx.ps1 .\diskonaut.exe -sRz # zipping is vital to avoid security layers and keep file metadata.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|