machineconfig 2.1__py3-none-any.whl → 2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of machineconfig might be problematic. Click here for more details.

Files changed (89) hide show
  1. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +2 -1
  2. machineconfig/cluster/templates/utils.py +0 -35
  3. machineconfig/jobs/python/check_installations.py +1 -1
  4. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  5. machineconfig/jobs/python_generic_installers/config.json +1 -1
  6. machineconfig/profile/create.py +10 -5
  7. machineconfig/profile/create_hardlinks.py +3 -1
  8. machineconfig/profile/shell.py +8 -7
  9. machineconfig/scripts/__init__.py +0 -2
  10. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  11. machineconfig/scripts/linux/devops +6 -4
  12. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  13. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  14. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  15. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  16. machineconfig/scripts/python/ai/generate_files.py +14 -15
  17. machineconfig/scripts/python/ai/mcinit.py +8 -5
  18. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  19. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  20. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  21. machineconfig/scripts/python/cloud_copy.py +22 -13
  22. machineconfig/scripts/python/cloud_mount.py +35 -23
  23. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  24. machineconfig/scripts/python/cloud_sync.py +4 -4
  25. machineconfig/scripts/python/croshell.py +37 -28
  26. machineconfig/scripts/python/devops.py +45 -27
  27. machineconfig/scripts/python/devops_add_identity.py +15 -25
  28. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  29. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  30. machineconfig/scripts/python/devops_devapps_install.py +25 -20
  31. machineconfig/scripts/python/devops_update_repos.py +142 -57
  32. machineconfig/scripts/python/dotfile.py +16 -14
  33. machineconfig/scripts/python/fire_agents.py +24 -17
  34. machineconfig/scripts/python/fire_jobs.py +91 -55
  35. machineconfig/scripts/python/ftpx.py +24 -14
  36. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  37. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  38. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  39. machineconfig/scripts/python/helpers/helpers4.py +44 -30
  40. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  41. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  42. machineconfig/scripts/python/mount_nfs.py +8 -15
  43. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  44. machineconfig/scripts/python/mount_ssh.py +8 -6
  45. machineconfig/scripts/python/repos.py +215 -57
  46. machineconfig/scripts/python/snapshot.py +0 -1
  47. machineconfig/scripts/python/start_slidev.py +10 -5
  48. machineconfig/scripts/python/start_terminals.py +22 -16
  49. machineconfig/scripts/python/viewer_template.py +0 -1
  50. machineconfig/scripts/python/wifi_conn.py +49 -75
  51. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  52. machineconfig/settings/lf/linux/lfrc +1 -0
  53. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  54. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  55. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  56. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  57. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  58. machineconfig/utils/code.py +3 -3
  59. machineconfig/utils/installer.py +2 -2
  60. machineconfig/utils/installer_utils/installer_abc.py +3 -4
  61. machineconfig/utils/installer_utils/installer_class.py +6 -4
  62. machineconfig/utils/links.py +103 -33
  63. machineconfig/utils/notifications.py +52 -38
  64. machineconfig/utils/options.py +16 -23
  65. machineconfig/utils/path_reduced.py +239 -205
  66. machineconfig/utils/procs.py +1 -1
  67. machineconfig/utils/source_of_truth.py +27 -0
  68. machineconfig/utils/ssh.py +9 -29
  69. machineconfig/utils/terminal.py +4 -2
  70. machineconfig/utils/upgrade_packages.py +91 -0
  71. machineconfig/utils/utils2.py +1 -2
  72. machineconfig/utils/utils5.py +23 -11
  73. machineconfig/utils/ve.py +4 -1
  74. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/METADATA +13 -13
  75. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/RECORD +78 -86
  76. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  77. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  78. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  79. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  80. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  81. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  82. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  84. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  85. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  86. machineconfig/utils/utils.py +0 -97
  87. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  88. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
  89. {machineconfig-2.1.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
@@ -33,31 +33,78 @@ def build_links(target_paths: list[tuple[PLike, str]], repo_root: PLike):
33
33
  links_path.symlink_to(target=a_target_path)
34
34
 
35
35
 
36
- def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool = True):
36
+ def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool):
37
37
  """helper function. creates a symlink from `this` to `to_this`.
38
- What can go wrong?
39
- depending on this and to_this existence, one will be prioretized depending on overwrite value.
40
- True means this will potentially be overwritten (depending on whether to_this exists or not)
41
- False means to_this will potentially be overwittten."""
38
+
39
+ this: exists AND to_this exists AND this is a symlink pointing to to_this ===> Resolution: AUTO: do nothing, already linked correctly.
40
+ 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: require user input to decide (param prioritize_to_this). Give 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
+
43
+ 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
+ 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)
45
+ this: exists AND to_this doesn't exist AND this is a concrete path ===> Resolution: AUTO: move this to to_this, then create symlink from this to to_this.
46
+
47
+ this: doesn't exist AND to_this exists ===> Resolution: AUTO: create link from this to to_this
48
+ this: doesn't exist AND to_this doesn't exist ===> Resolution: AUTO: create link from this to to_this (touch to_this)
49
+
50
+ """
42
51
  this = PathExtended(this).expanduser().absolute()
43
52
  to_this = PathExtended(to_this).expanduser().absolute()
44
- if this.is_symlink():
45
- this.delete(sure=True) # delete if it exists as symblic link, not a concrete path.
46
- if this.exists(): # this is a problem. It will be resolved via `overwrite`
47
- if prioritize_to_this is True: # it *can* be deleted, but let's look at target first.
48
- if to_this.exists(): # this exists, to_this as well. to_this is prioritized.
49
- this.append(f".orig_{randstr()}", inplace=True) # rename is better than deletion
53
+ # Case analysis based on docstring
54
+ if this.exists():
55
+ if to_this.exists():
56
+ if this.is_symlink():
57
+ # Check if symlink already points to correct target
58
+ try:
59
+ if this.readlink().resolve() == to_this.resolve():
60
+ # Case: this exists AND to_this exists AND this is a symlink pointing to to_this
61
+ console.print(Panel(f"✅ ALREADY LINKED | {this} ➡️ {to_this}", title="Already Linked", expand=False))
62
+ return
63
+ else:
64
+ # Case: this exists AND to_this exists AND this is a symlink pointing to somewhere else
65
+ console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
66
+ this.delete(sure=True)
67
+ except OSError:
68
+ # Broken symlink case
69
+ console.print(Panel(f"🔄 FIXING BROKEN LINK | Fixing broken symlink from {this} ➡️ {to_this}", title="Fixing Broken Link", expand=False))
70
+ this.delete(sure=True)
50
71
  else:
51
- this.move(path=to_this) # this exists, to_this doesn't. to_this is prioritized.
52
- elif prioritize_to_this is False: # don't sacrefice this, sacrefice to_this.
53
- if to_this.exists():
54
- this.move(path=to_this, overwrite=True) # this exists, to_this as well, this is prioritized. # now we are readly to make the link
72
+ # Case: this exists AND to_this exists AND this is a concrete path
73
+ if prioritize_to_this:
74
+ # prioritize `to_this`: `this` is backed up, `this` is deleted, symlink created
75
+ backup_name = f"{this}.orig_{randstr()}"
76
+ console.print(Panel(f"📦 BACKING UP | Moving {this} to {backup_name}, prioritizing {to_this}", title="Backing Up", expand=False))
77
+ this.move(path=backup_name)
78
+ else:
79
+ # prioritize `this`: to_this is backed up, to_this is deleted, this content moved to to_this location
80
+ backup_name = f"{to_this}.orig_{randstr()}"
81
+ console.print(Panel(f"📦 BACKING UP | Moving {to_this} to {backup_name}, prioritizing {this}", title="Backing Up", expand=False))
82
+ to_this.move(path=backup_name)
83
+ this.move(path=to_this)
84
+ else:
85
+ # to_this doesn't exist
86
+ if this.is_symlink():
87
+ # Case: this exists AND to_this doesn't exist AND this is a symlink (pointing anywhere)
88
+ console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
89
+ this.delete(sure=True)
90
+ # Create to_this
91
+ to_this.parent.mkdir(parents=True, exist_ok=True)
92
+ to_this.touch()
55
93
  else:
56
- this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
57
- else: # this doesn't exist.
58
- if not to_this.exists():
94
+ # Case: this exists AND to_this doesn't exist AND this is a concrete path
95
+ console.print(Panel(f"📁 MOVING | Moving {this} to {to_this}, then creating symlink", title="Moving", expand=False))
96
+ this.move(path=to_this)
97
+ else:
98
+ # this doesn't exist
99
+ if to_this.exists():
100
+ # Case: this doesn't exist AND to_this exists
101
+ console.print(Panel(f"🆕 NEW LINK | Creating new symlink from {this} ➡️ {to_this}", title="New Link", expand=False))
102
+ else:
103
+ # Case: this doesn't exist AND to_this doesn't exist
104
+ console.print(Panel(f"🆕 NEW LINK & TARGET | Creating {to_this} and symlink from {this} ➡️ {to_this}", title="New Link & Target", expand=False))
59
105
  to_this.parent.mkdir(parents=True, exist_ok=True)
60
- to_this.touch() # we have to touch it (file) or create it (folder)
106
+ to_this.touch()
107
+ # Create the symlink
61
108
  try:
62
109
  console.print(Panel(f"🔗 LINKING | Creating symlink from {this} ➡️ {to_this}", title="Linking", expand=False))
63
110
  PathExtended(this).symlink_to(target=to_this, verbose=True, overwrite=True)
@@ -68,23 +115,46 @@ def symlink_func(this: PathExtended, to_this: PathExtended, prioritize_to_this:
68
115
  def symlink_copy(this: PathExtended, to_this: PathExtended, prioritize_to_this: bool = True):
69
116
  this = PathExtended(this).expanduser().absolute()
70
117
  to_this = PathExtended(to_this).expanduser().absolute()
71
- if this.is_symlink():
72
- this.delete(sure=True) # delete if it exists as symblic link, not a concrete path.
73
- if this.exists(): # this is a problem. It will be resolved via `overwrite`
74
- if prioritize_to_this is True: # it *can* be deleted, but let's look at target first.
75
- if to_this.exists(): # this exists, to_this as well. to_this is prioritized.
76
- this.append(f".orig_{randstr()}", inplace=True) # rename is better than deletion
118
+ # Case analysis based on docstring of symlink_func
119
+ if this.exists():
120
+ if to_this.exists():
121
+ if this.is_symlink():
122
+ try:
123
+ if this.readlink().resolve() == to_this.resolve():
124
+ console.print(Panel(f"✅ ALREADY LINKED | {this} ➡️ {to_this}", title="Already Linked", expand=False))
125
+ return
126
+ else:
127
+ console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
128
+ this.delete(sure=True)
129
+ except OSError:
130
+ console.print(Panel(f"🔄 FIXING BROKEN LINK | Fixing broken symlink from {this} ➡️ {to_this}", title="Fixing Broken Link", expand=False))
131
+ this.delete(sure=True)
77
132
  else:
78
- this.move(path=to_this) # this exists, to_this doesn't. to_this is prioritized.
79
- elif prioritize_to_this is False: # don't sacrefice this, sacrefice to_this.
80
- if to_this.exists():
81
- this.move(path=to_this, overwrite=True) # this exists, to_this as well, this is prioritized. # now we are readly to make the link
133
+ if prioritize_to_this:
134
+ backup_name = f"{this}.orig_{randstr()}"
135
+ console.print(Panel(f"📦 BACKING UP | Moving {this} to {backup_name}, prioritizing {to_this}", title="Backing Up", expand=False))
136
+ this.move(path=backup_name)
137
+ else:
138
+ backup_name = f"{to_this}.orig_{randstr()}"
139
+ console.print(Panel(f"📦 BACKING UP | Moving {to_this} to {backup_name}, prioritizing {this}", title="Backing Up", expand=False))
140
+ to_this.move(path=backup_name)
141
+ this.move(path=to_this)
142
+ else:
143
+ if this.is_symlink():
144
+ console.print(Panel(f"🔄 RELINKING | Updating symlink from {this} ➡️ {to_this}", title="Relinking", expand=False))
145
+ this.delete(sure=True)
146
+ to_this.parent.mkdir(parents=True, exist_ok=True)
147
+ to_this.touch()
82
148
  else:
83
- this.move(path=to_this) # this exists, to_this doesn't, this is prioritized.
84
- else: # this doesn't exist.
85
- if not to_this.exists():
149
+ console.print(Panel(f"📁 MOVING | Moving {this} to {to_this}, then copying", title="Moving", expand=False))
150
+ this.move(path=to_this)
151
+ else:
152
+ if to_this.exists():
153
+ console.print(Panel(f"🆕 NEW LINK | Copying {to_this} to {this}", title="New Link", expand=False))
154
+ else:
155
+ console.print(Panel(f"🆕 NEW LINK & TARGET | Creating {to_this} and copying to {this}", title="New Link & Target", expand=False))
86
156
  to_this.parent.mkdir(parents=True, exist_ok=True)
87
- to_this.touch() # we have to touch it (file) or create it (folder)
157
+ to_this.touch()
88
158
  try:
89
159
  console.print(Panel(f"📋 COPYING | Copying {to_this} to {this}", title="Copying", expand=False))
90
160
  to_this.copy(path=this, overwrite=True, verbose=True)
@@ -1,30 +1,32 @@
1
-
2
- """Notifications Module
3
- """
1
+ """Notifications Module"""
4
2
 
5
3
  # from crocodile.core import install_n_import
6
4
  # from crocodile.file_management import P, Read
7
5
  from pathlib import Path
6
+
8
7
  # from crocodile.meta import RepeatUntilNoException
9
8
  import smtplib
10
9
  import imaplib
10
+
11
11
  # from email import message
12
12
  # from email import encoders
13
13
  # from email.mime.base import MIMEBase
14
14
  from email.mime.text import MIMEText
15
15
  from email.mime.multipart import MIMEMultipart
16
- from typing import Optional, Any, Union, Literal
16
+ from typing import Optional, Any, Union
17
17
  from markdown import markdown
18
18
 
19
19
 
20
-
21
- def download_to_memory(path: Path, allow_redirects: bool = True, timeout: Optional[float] = None, params: Any = None) -> 'Any':
20
+ def download_to_memory(path: Path, allow_redirects: bool = True, timeout: Optional[float] = None, params: Any = None) -> "Any":
22
21
  import requests
23
- return requests.get(path.as_posix().replace("https:/", "https://").replace("http:/", "http://"), allow_redirects=allow_redirects, timeout=timeout, params=params) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
22
+
23
+ return requests.get(
24
+ path.as_posix().replace("https:/", "https://").replace("http:/", "http://"), allow_redirects=allow_redirects, timeout=timeout, params=params
25
+ ) # Alternative: from urllib import request; request.urlopen(url).read().decode('utf-8').
24
26
 
25
27
 
26
28
  def get_github_markdown_css() -> str:
27
- pp = r'https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown-dark.css'
29
+ pp = r"https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown-dark.css"
28
30
  return download_to_memory(Path(pp)).text
29
31
 
30
32
 
@@ -75,8 +77,10 @@ encryption = ssl
75
77
 
76
78
  """)
77
79
 
78
- if not Path(path).exists() or Path(path).is_dir(): raise FileNotFoundError(f"File not found or is a directory: {path}")
80
+ if not Path(path).exists() or Path(path).is_dir():
81
+ raise FileNotFoundError(f"File not found or is a directory: {path}")
79
82
  import configparser
83
+
80
84
  res = configparser.ConfigParser()
81
85
  res.read(filenames=[str(path)], encoding=None)
82
86
  return res
@@ -84,10 +88,13 @@ encryption = ssl
84
88
  def __init__(self, config: dict[str, Any]):
85
89
  self.config = config
86
90
  from smtplib import SMTP_SSL, SMTP
91
+
87
92
  self.server: Union[SMTP_SSL, SMTP]
88
- if config['encryption'].lower() == "ssl": self.server = smtplib.SMTP_SSL(host=self.config["smtp_host"], port=self.config["smtp_port"])
89
- elif config['encryption'].lower() == "tls": self.server = smtplib.SMTP(host=self.config["smtp_host"], port=self.config["smtp_port"])
90
- self.server.login(self.config['email_add'], password=self.config["password"])
93
+ if config["encryption"].lower() == "ssl":
94
+ self.server = smtplib.SMTP_SSL(host=self.config["smtp_host"], port=self.config["smtp_port"])
95
+ elif config["encryption"].lower() == "tls":
96
+ self.server = smtplib.SMTP(host=self.config["smtp_host"], port=self.config["smtp_port"])
97
+ self.server.login(self.config["email_add"], password=self.config["password"])
91
98
 
92
99
  def send_message(self, to: str, subject: str, body: str, txt_to_html: bool = True, attachments: Optional[list[Any]] = None):
93
100
  _ = attachments
@@ -95,7 +102,7 @@ encryption = ssl
95
102
  # msg = message.EmailMessage()
96
103
  msg = MIMEMultipart("alternative")
97
104
  msg["subject"] = subject
98
- msg["From"] = self.config['email_add']
105
+ msg["From"] = self.config["email_add"]
99
106
  msg["To"] = to
100
107
  # msg['Content-Type'] = "text/html"
101
108
  # msg.set_content(body)
@@ -103,7 +110,8 @@ encryption = ssl
103
110
  # <link rel="stylesheet" href="github-markdown.css">
104
111
  # <link type="text/css" rel="stylesheet" href="https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown-dark.css" />
105
112
 
106
- if txt_to_html: body = md2html(body=body)
113
+ if txt_to_html:
114
+ body = md2html(body=body)
107
115
  msg.attach(MIMEText(body, "html"))
108
116
  # if attachments is None: attachments = [] # see: https://fedingo.com/how-to-send-html-mail-with-attachment-using-python/
109
117
  # for attachment in attachmenthrs: msg.attach(attachment.read_bytes(), filename=attachment.stem, maintype="image", subtype=attachment.suffix)
@@ -117,14 +125,19 @@ encryption = ssl
117
125
  server.starttls()
118
126
  server.login(email_add, password=pwd)
119
127
 
120
- def send_email(self, to_addrs: str, msg: str): return self.server.sendmail(from_addr=self.config['email_add'], to_addrs=to_addrs, msg=msg)
121
- def close(self): self.server.quit() # Closing is vital as many servers do not allow mutiple connections.
128
+ def send_email(self, to_addrs: str, msg: str):
129
+ return self.server.sendmail(from_addr=self.config["email_add"], to_addrs=to_addrs, msg=msg)
130
+
131
+ def close(self):
132
+ self.server.quit() # Closing is vital as many servers do not allow mutiple connections.
122
133
 
123
134
  @staticmethod
124
135
  def send_and_close(config_name: Optional[str], to: str, subject: str, body: str) -> Any:
125
136
  """If config_name is None, it sends from a generic email address."""
126
137
  if config_name is None:
127
- raise NotImplementedError("Sending email without a config_name is not implemented. You need to create an emails.ini file in ~/dotfiles/machineconfig/ with your email configuration. See the docstring of the get_source_of_truth method for more information.")
138
+ raise NotImplementedError(
139
+ "Sending email without a config_name is not implemented. You need to create an emails.ini file in ~/dotfiles/machineconfig/ with your email configuration. See the docstring of the get_source_of_truth method for more information."
140
+ )
128
141
  # config = Email.get_source_of_truth()
129
142
  # try:
130
143
  # api_key = config['resend']['api_key']
@@ -150,26 +163,27 @@ encryption = ssl
150
163
  tmp.send_message(to=to, subject=subject, body=body)
151
164
  tmp.close()
152
165
 
153
- @staticmethod
154
- def send_m365(to: list[str], subject: str, body: Optional[str], body_file: Optional[str], body_content_type: Literal["HTML", "Text"], attachments: Optional[list[Path]] = None) -> None:
155
- if body_file is not None:
156
- assert body is None, "You cannot pass both body and body_file."
157
- body_file_path = Path(body_file)
158
- assert body_file_path.exists(), f"File not found: {body_file_path}"
159
- else:
160
- body_file_path = None
161
- assert body is not None, "You must pass either body or body_file."
162
- from crocodile.meta import Terminal
163
- to_str = ",".join(to)
164
- attachments_str = " ".join([f"--attachment {str(p)}" for p in attachments]) if attachments is not None else ""
165
-
166
- if body_file is not None:
167
- body_arg = f"--bodyContents @{body_file_path}"
168
- else:
169
- body_arg = f'"{body}"'
170
- cmd = f"""m365 outlook mail send --verbose --saveToSentItems --importance normal --bodyContentType {body_content_type} --bodyContents {body_arg} --subject "{subject}" --to {to_str} {attachments_str}"""
171
- response = Terminal().run(cmd, shell="powershell")
172
- response.print(desc="Email sending response")
166
+ # @staticmethod
167
+ # def send_m365(to: list[str], subject: str, body: Optional[str], body_file: Optional[str], body_content_type: Literal["HTML", "Text"], attachments: Optional[list[Path]] = None) -> None:
168
+ # if body_file is not None:
169
+ # assert body is None, "You cannot pass both body and body_file."
170
+ # body_file_path = Path(body_file)
171
+ # assert body_file_path.exists(), f"File not found: {body_file_path}"
172
+ # else:
173
+ # body_file_path = None
174
+ # assert body is not None, "You must pass either body or body_file."
175
+ # from crocodile.meta import Terminal
176
+
177
+ # to_str = ",".join(to)
178
+ # attachments_str = " ".join([f"--attachment {str(p)}" for p in attachments]) if attachments is not None else ""
179
+
180
+ # if body_file is not None:
181
+ # body_arg = f"--bodyContents @{body_file_path}"
182
+ # else:
183
+ # body_arg = f'"{body}"'
184
+ # cmd = f"""m365 outlook mail send --verbose --saveToSentItems --importance normal --bodyContentType {body_content_type} --bodyContents {body_arg} --subject "{subject}" --to {to_str} {attachments_str}"""
185
+ # response = Terminal().run(cmd, shell="powershell")
186
+ # response.print(desc="Email sending response")
173
187
 
174
188
 
175
189
  # class PhoneNotification: # security concerns: avoid using this.
@@ -193,5 +207,5 @@ encryption = ssl
193
207
  # n.send_notification()
194
208
 
195
209
 
196
- if __name__ == '__main__':
210
+ if __name__ == "__main__":
197
211
  pass
@@ -9,34 +9,27 @@ from typing import Optional, Union, TypeVar, Iterable
9
9
  T = TypeVar("T")
10
10
 
11
11
 
12
- def check_tool_exists(tool_name: str, install_script: Optional[str] = None) -> bool:
13
- if platform.system() == "Windows":
14
- tool_name = tool_name.replace(".exe", "") + ".exe"
12
+ def check_tool_exists(tool_name: str) -> bool:
13
+ if platform.system() == "Windows": tool_name = tool_name.replace(".exe", "") + ".exe"
14
+
15
+ from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
15
16
 
16
17
  if platform.system() == "Windows":
17
18
  cmd = "where.exe"
19
+ root_path = Path(WINDOWS_INSTALL_PATH)
18
20
  elif platform.system() in ["Linux", "Darwin"]:
19
21
  cmd = "which"
20
- else:
21
- raise NotImplementedError(f"platform {platform.system()} not implemented")
22
-
23
- try:
24
- _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
25
- res: bool = True
26
- except (subprocess.CalledProcessError, FileNotFoundError):
27
- res = False
28
- if res is False and install_script is not None:
29
- console = Console()
30
- console.print(Panel(f"📥 INSTALLING TOOL | Installing {tool_name}...", border_style="bold blue", expand=False))
31
- result = subprocess.run(install_script, shell=True, capture_output=True, text=True)
32
- print(f"Command: {install_script}")
33
- if result.stdout:
34
- print(f"STDOUT: {result.stdout}")
35
- if result.stderr:
36
- print(f"STDERR: {result.stderr}")
37
- print(f"Return code: {result.returncode}")
38
- return check_tool_exists(tool_name=tool_name, install_script=None)
39
- return res
22
+ root_path = Path(LINUX_INSTALL_PATH)
23
+ else: raise NotImplementedError(f"platform {platform.system()} not implemented")
24
+
25
+ _ = cmd
26
+ # try:
27
+ # _tmp = subprocess.check_output([cmd, tool_name], stderr=subprocess.DEVNULL)
28
+ # res: bool = True
29
+ # except (subprocess.CalledProcessError, FileNotFoundError):
30
+ # res = False
31
+ # return res
32
+ return root_path.joinpath(tool_name).is_file()
40
33
 
41
34
 
42
35
  def choose_one_option(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T: