machineconfig 1.7__py3-none-any.whl → 1.9__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 (72) hide show
  1. machineconfig/__init__.py +4 -2
  2. machineconfig/jobs/python/check_installations.py +38 -32
  3. machineconfig/jobs/python/create_bootable_media.py +4 -4
  4. machineconfig/jobs/python/create_zellij_template.py +3 -2
  5. machineconfig/jobs/python/python_cargo_build_share.py +14 -9
  6. machineconfig/jobs/python/python_ve_symlink.py +6 -6
  7. machineconfig/jobs/python_custom_installers/azuredatastudio.py +36 -0
  8. machineconfig/jobs/python_custom_installers/bypass_paywall.py +30 -0
  9. machineconfig/jobs/{python_linux_installers/dev → python_custom_installers}/docker_desktop.py +15 -4
  10. machineconfig/jobs/python_custom_installers/gh.py +53 -0
  11. machineconfig/jobs/python_custom_installers/goes.py +35 -0
  12. machineconfig/jobs/python_custom_installers/helix.py +43 -0
  13. machineconfig/jobs/python_custom_installers/lvim.py +48 -0
  14. machineconfig/jobs/python_custom_installers/ngrok.py +39 -0
  15. machineconfig/jobs/python_custom_installers/nvim.py +48 -0
  16. machineconfig/jobs/python_custom_installers/vscode.py +45 -0
  17. machineconfig/jobs/python_custom_installers/wezterm.py +41 -0
  18. machineconfig/profile/create.py +12 -7
  19. machineconfig/profile/shell.py +15 -13
  20. machineconfig/scripts/python/choose_wezterm_theme.py +96 -0
  21. machineconfig/scripts/python/cloud_copy.py +18 -13
  22. machineconfig/scripts/python/cloud_mount.py +41 -15
  23. machineconfig/scripts/python/cloud_repo_sync.py +57 -31
  24. machineconfig/scripts/python/cloud_sync.py +13 -15
  25. machineconfig/scripts/python/croshell.py +19 -10
  26. machineconfig/scripts/python/devops.py +22 -6
  27. machineconfig/scripts/python/devops_add_identity.py +7 -6
  28. machineconfig/scripts/python/devops_add_ssh_key.py +10 -9
  29. machineconfig/scripts/python/devops_backup_retrieve.py +5 -5
  30. machineconfig/scripts/python/devops_devapps_install.py +24 -19
  31. machineconfig/scripts/python/devops_update_repos.py +5 -4
  32. machineconfig/scripts/python/dotfile.py +8 -4
  33. machineconfig/scripts/python/fire_jobs.py +165 -55
  34. machineconfig/scripts/python/ftpx.py +18 -8
  35. machineconfig/scripts/python/mount_nfs.py +13 -10
  36. machineconfig/scripts/python/mount_nw_drive.py +4 -3
  37. machineconfig/scripts/python/mount_ssh.py +8 -5
  38. machineconfig/scripts/python/repos.py +26 -21
  39. machineconfig/scripts/python/snapshot.py +2 -2
  40. machineconfig/scripts/python/start_slidev.py +104 -0
  41. machineconfig/scripts/python/start_terminals.py +1 -1
  42. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +20 -34
  43. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +11 -12
  44. machineconfig/utils/installer.py +177 -217
  45. machineconfig/utils/scheduling.py +1 -1
  46. machineconfig/utils/utils.py +107 -54
  47. machineconfig/utils/ve.py +120 -16
  48. machineconfig-1.9.dist-info/LICENSE +201 -0
  49. {machineconfig-1.7.dist-info → machineconfig-1.9.dist-info}/METADATA +155 -140
  50. machineconfig-1.9.dist-info/RECORD +76 -0
  51. {machineconfig-1.7.dist-info → machineconfig-1.9.dist-info}/WHEEL +1 -1
  52. machineconfig/jobs/python_generic_installers/archive/gopass.py +0 -18
  53. machineconfig/jobs/python_generic_installers/archive/nvim.py +0 -20
  54. machineconfig/jobs/python_generic_installers/archive/opencommit.py +0 -25
  55. machineconfig/jobs/python_generic_installers/archive/strongbox.py +0 -33
  56. machineconfig/jobs/python_generic_installers/dev/__init__.py +0 -0
  57. machineconfig/jobs/python_linux_installers/archive/__init__.py +0 -0
  58. machineconfig/jobs/python_linux_installers/archive/bandwhich.py +0 -14
  59. machineconfig/jobs/python_linux_installers/archive/ranger.py +0 -19
  60. machineconfig/jobs/python_linux_installers/dev/azure_data_studio.py +0 -21
  61. machineconfig/jobs/python_linux_installers/dev/bytehound.py +0 -20
  62. machineconfig/jobs/python_linux_installers/dev/nnn.py +0 -22
  63. machineconfig/jobs/python_windows_installers/archive/ntop.py +0 -21
  64. machineconfig/jobs/python_windows_installers/dev/bypass_paywall.py +0 -22
  65. machineconfig/jobs/python_windows_installers/dev/obs_background_removal_plugin.py +0 -22
  66. machineconfig/scripts/python/choose_ohmybash_theme.py +0 -31
  67. machineconfig/scripts/python/choose_ohmyposh_theme.py +0 -57
  68. machineconfig/utils/pandas_type.py +0 -37
  69. machineconfig/utils/to_exe.py +0 -7
  70. machineconfig-1.7.dist-info/RECORD +0 -81
  71. /machineconfig/jobs/{python_generic_installers/archive → python_custom_installers}/__init__.py +0 -0
  72. {machineconfig-1.7.dist-info → machineconfig-1.9.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,12 @@
1
1
 
2
- import argparse
3
- import crocodile.toolbox as tb
2
+ """Like yadm and dotter.
3
+ """
4
+
5
+
6
+ from crocodile.file_management import P
4
7
  from machineconfig.profile.create import symlink
5
8
  from machineconfig.utils.utils import LIBRARY_ROOT, REPO_ROOT
9
+ import argparse
6
10
 
7
11
 
8
12
  def main():
@@ -15,14 +19,14 @@ def main():
15
19
  parser.add_argument("-d", "--dest", help=f"destination folder", default="")
16
20
 
17
21
  args = parser.parse_args()
18
- orig_path = tb.P(args.file).expanduser().absolute()
22
+ orig_path = P(args.file).expanduser().absolute()
19
23
  if args.dest == "":
20
24
  if "Local" in orig_path: junction = orig_path.split(at="Local", sep=-1)[1]
21
25
  elif "Roaming" in orig_path: junction = orig_path.split(at="Roaming", sep=-1)[1]
22
26
  elif ".config" in orig_path: junction = orig_path.split(at=".config", sep=-1)[1]
23
27
  else: junction = orig_path.rel2home()
24
28
  new_path = REPO_ROOT.joinpath(junction)
25
- else: new_path = tb.P(args.dest).expanduser().absolute().create().joinpath(orig_path.name)
29
+ else: new_path = P(args.dest).expanduser().absolute().create().joinpath(orig_path.name)
26
30
 
27
31
  symlink(this=orig_path, to_this=new_path, prioritize_to_this=args.overwrite)
28
32
 
@@ -1,14 +1,17 @@
1
1
 
2
2
  """
3
3
  fire
4
- """
5
4
 
6
- import crocodile.toolbox as tb
7
- from machineconfig.utils.utils import display_options, PROGRAM_PATH, choose_ssh_host, match_file_name, sanitize_path
8
- # from crocodile.run import *
9
5
  # https://github.com/pallets/click combine with fire. Consider
10
6
  # https://github.com/ceccopierangiolieugenio/pyTermTk for display_options build TUI
11
7
  # https://github.com/chriskiehl/Gooey build commandline interface
8
+
9
+ """
10
+
11
+
12
+ from machineconfig.utils.utils import display_options, choose_one_option, PROGRAM_PATH, choose_ssh_host, match_file_name, sanitize_path
13
+ from crocodile.file_management import P, install_n_import
14
+ from crocodile.core import Display, randstr
12
15
  import inspect
13
16
  import platform
14
17
  import os
@@ -16,22 +19,25 @@ from typing import Callable, Any, Optional
16
19
  import argparse
17
20
 
18
21
 
19
- def main():
22
+ def main() -> None:
20
23
  parser = argparse.ArgumentParser()
21
- parser.add_argument("path", nargs='?', type=str, help="The directory containing the jobs", default=".")
24
+ parser.add_argument("path", nargs='?', type=str, help="The directory containing the jobs", default=".")
22
25
  parser.add_argument("function", nargs='?', type=str, help="Fuction to run", default=None)
23
26
  # parser.add_argument("--function", "-f", type=str, help="The function to run", default="")
24
- parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
25
- parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
26
- parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
27
- parser.add_argument("--debug", "-d", action="store_true", help="debug")
27
+ parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
28
+ parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
29
+ parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
30
+ parser.add_argument("--debug", "-d", action="store_true", help="debug")
28
31
  parser.add_argument("--choose_function", "-c", action="store_true", help="debug")
29
- parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion)")
32
+ parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion)")
33
+ parser.add_argument("--jupyter", "-j", action="store_true", help="open in a jupyter notebook")
30
34
  parser.add_argument("--submit_to_cloud", "-C", action="store_true", help="submit to cloud compute")
31
- parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
32
- parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
33
- parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
34
- parser.add_argument("--history", "-H", action="store_true", help="choose from history")
35
+ parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
36
+ parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
37
+ parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
38
+ parser.add_argument("--history", "-H", action="store_true", help="choose from history")
39
+ # parser.add_argument("--git_pull", "-g", action="store_true", help="Start by pulling the git repo")
40
+ parser.add_argument("--Nprocess", "-p", type=int, help="Number of processes to use", default=1)
35
41
  parser.add_argument("--kw", nargs="*", default=None, help="keyword arguments to pass to the function in the form of k1 v1 k2 v2 ...")
36
42
 
37
43
  args = parser.parse_args()
@@ -42,8 +48,11 @@ def main():
42
48
  else:
43
49
  kwargs = {}
44
50
 
45
- path_obj = sanitize_path(tb.P(args.path))
46
- if not path_obj.exists(): path_obj = match_file_name(args.path)
51
+ path_obj = sanitize_path(P(args.path))
52
+ if not path_obj.exists():
53
+ path_obj = match_file_name(args.path)
54
+ print(path_obj)
55
+ else: pass
47
56
 
48
57
  if path_obj.is_dir():
49
58
  print(f"Seaching recursively for all python file in directory `{path_obj}`")
@@ -52,18 +61,16 @@ def main():
52
61
  sh_files = path_obj.search(pattern="*.sh", r=True).list
53
62
  files = py_files + ps_files + sh_files
54
63
 
55
- choice_file = display_options(msg="Choose a file to run", options=files, fzf=True, multi=False)
56
- assert not isinstance(choice_file, list), f"choice_file must be a string. Got {type(choice_file)}"
57
- choice_file = tb.P(choice_file)
64
+ choice_file = choose_one_option(options=files, fzf=True)
65
+ choice_file = P(choice_file)
58
66
  else:
59
67
  choice_file = path_obj
60
68
 
61
69
  if choice_file.suffix in [".ps1", ".sh"]:
62
70
  PROGRAM_PATH.write_text(f". {choice_file}")
63
- return
71
+ return None
64
72
 
65
73
  if args.choose_function or args.submit_to_cloud:
66
-
67
74
  options, func_args = parse_pyfile(file_path=str(choice_file))
68
75
  choice_function_tmp = display_options(msg="Choose a function to run", options=options, fzf=True, multi=False)
69
76
  assert isinstance(choice_function_tmp, str), f"choice_function must be a string. Got {type(choice_function_tmp)}"
@@ -75,39 +82,55 @@ def main():
75
82
  if len(choice_function_args) > 0 and len(kwargs) == 0:
76
83
  for item in choice_function_args:
77
84
  kwargs[item.name] = input(f"Please enter a value for argument `{item.name}` (type = {item.type}) (default = {item.default}) : ") or item.default
78
- else: choice_function = args.function
85
+ else:
86
+ choice_function = args.function
79
87
 
80
88
  if args.ve == "":
81
89
  from machineconfig.utils.ve import get_ve_profile # if file name is passed explicitly, then, user probably launched it from cwd different to repo root, so activate_ve can't infer ve from .ve_path, so we attempt to do that manually here
82
90
  args.ve = get_ve_profile(choice_file)
83
91
 
84
- if args.streamlit: exe = "streamlit run"
92
+ if args.streamlit:
93
+ import socket
94
+ try: local_ip_v4 = socket.gethostbyname(socket.gethostname() + ".local") # without .local, in linux machines, '/etc/hosts' file content, you have an IP address mapping with '127.0.1.1' to your hostname
95
+ except Exception:
96
+ print(f"Warning: Could not get local_ip_v4. This is probably because you are running a WSL instance") # TODO find a way to get the local_ip_v4 in WSL
97
+ local_ip_v4 = socket.gethostbyname(socket.gethostname())
98
+ print(f"🚀 Streamlit app is running at: http://{local_ip_v4}:8501")
99
+ exe = "streamlit run --server.address 0.0.0.0 --server.headless true"
85
100
  elif args.interactive is False: exe = "python"
101
+ elif args.jupyter: exe = "jupyter-lab"
86
102
  else:
87
103
  from machineconfig.utils.ve import get_ipython_profile
88
104
  exe = f"ipython -i --no-banner --profile {get_ipython_profile(choice_file)} "
89
105
 
90
106
  if args.module or (args.debug and args.choose_function): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
107
+ import_line = get_import_module_code(str(choice_file))
91
108
  txt: str = f"""
92
109
  try:
93
- {get_import_module_code(str(choice_file))}
110
+ {import_line}
94
111
  except (ImportError, ModuleNotFoundError) as ex:
95
- print(fr"Failed to import {choice_file} the proper way. {{ex}} ")
96
- print(fr"The way below is rather hacky and can cause issues in pickling.")
112
+ print(fr"Failed to import `{choice_file}` the proper way. {{ex}} ")
113
+ print(fr"Importing with an ad-hoc `$PATH` manipulation. DO NOT pickle any files in this session as there is no gaurantee of correct deserialization.")
97
114
  import sys
98
- sys.path.append(r'{tb.P(choice_file).parent}')
99
- from {tb.P(choice_file).stem} import *
115
+ sys.path.append(r'{P(choice_file).parent}')
116
+ from {P(choice_file).stem} import *
100
117
 
101
118
  """
102
119
  if choice_function is not None:
103
120
  txt = txt + f"""
104
- {choice_function}({('**' + str(kwargs)) if kwargs else ''})
121
+ res = {choice_function}({('**' + str(kwargs)) if kwargs else ''})
105
122
  """
106
123
  txt = f"""
107
- from machineconfig.utils.utils import print_code
108
- print_code(code=r'''{txt}''', lexer='python', desc='Imported Script')
124
+ try:
125
+ from rich.panel import Panel
126
+ from rich.console import Console
127
+ from rich.syntax import Syntax
128
+ console = Console()
129
+ console.print(Panel(Syntax(code=r'''{txt}''', lexer='python'), title='Import Script'), style="bold red")
130
+ except ImportError as _ex:
131
+ print(r'''{txt}''')
109
132
  """ + txt
110
- choice_file = tb.P.tmp().joinpath(f'tmp_scripts/python/{tb.P(choice_file).parent.name}_{tb.P(choice_file).stem}_{tb.randstr()}.py').create(parents_only=True).write_text(txt)
133
+ choice_file = P.tmp().joinpath(f'tmp_scripts/python/{P(choice_file).parent.name}_{P(choice_file).stem}_{randstr()}.py').create(parents_only=True).write_text(txt)
111
134
 
112
135
  # determining basic command structure: putting together exe & choice_file & choice_function & pdb
113
136
  if args.debug:
@@ -118,8 +141,24 @@ print_code(code=r'''{txt}''', lexer='python', desc='Imported Script')
118
141
  else: raise NotImplementedError(f"Platform {platform.system()} not supported.")
119
142
  elif choice_function is not None and not args.module: # if args.module, then kwargs are handled in the impot script, no need to pass them in fire command.
120
143
  # https://google.github.io/python-fire/guide/
121
- tmp = f"'{kwargs}'" if kwargs else ''
122
- command = f"{exe} -m fire {choice_file} {choice_function} {tmp}"
144
+ # https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing
145
+ if not kwargs: # empty dict
146
+ kwargs_str = ''
147
+ else:
148
+ if len(kwargs) == 1:
149
+ kwargs_str = f""" --{list(kwargs.keys())[0]} {list(kwargs.values())[0]} """
150
+ else:
151
+ # print(f"len(kwargs) = {len(kwargs)}")
152
+ tmp_list: list[str] = []
153
+ for k, v in kwargs.items():
154
+ if v is not None:
155
+ item = f'"{k}": "{v}"'
156
+ else:
157
+ item = f'"{k}": None'
158
+ tmp_list.append(item)
159
+ tmp__ = ", ".join(tmp_list)
160
+ kwargs_str = "'{" + tmp__ + "}'"
161
+ command = f"{exe} -m fire {choice_file} {choice_function} {kwargs_str}"
123
162
  # else:
124
163
  # print(f"{kwargs=}")
125
164
  # print(f"{choice_function_args=}")
@@ -131,16 +170,26 @@ print_code(code=r'''{txt}''', lexer='python', desc='Imported Script')
131
170
  else:
132
171
  if not args.cmd:
133
172
  # for .streamlit config to work, it needs to be in the current directory.
134
- command = f"cd {choice_file.parent}; {exe} {choice_file.name}; cd {tb.P.cwd()}"
135
- else: command = rf""" cd /d {choice_file.parent} & {exe} {choice_file.name} """
136
- # command = f"cd {choice_file.parent}; {exe} {choice_file.name}; cd {tb.P.cwd()}"
173
+ command = f"cd {choice_file.parent}\n\n{exe} {choice_file.name}\n\ncd {P.cwd()}"
174
+ else:
175
+ command = rf""" cd /d {choice_file.parent} & {exe} {choice_file.name} """
176
+ # command = f"cd {choice_file.parent}\n\n{exe} {choice_file.name}\n\ncd {P.cwd()}"
137
177
 
138
- if "ipdb" in command: tb.install_n_import("ipdb")
139
- if "pudb" in command: tb.install_n_import("pudb")
178
+ # this installs in ve env, which is not execution env
179
+ # if "ipdb" in command: install_n_import("ipdb")
180
+ # if "pudb" in command: install_n_import("pudb")
140
181
 
141
182
  if not args.cmd:
142
- command = f". activate_ve {args.ve}; {command}"
183
+ if "ipdb" in command: command = f"pip install ipdb\n\n{command}"
184
+ if "pudb" in command: command = f"pip install pudb\n\n{command}"
185
+ if platform.system() == "Windows":
186
+ command = f". activate_ve {args.ve}\n\n{command}"
187
+ else:
188
+ command = f". ~/scripts/activate_ve {args.ve}\n\n{command}"
143
189
  else:
190
+ # CMD equivalent
191
+ if "ipdb" in command: command = f"pip install ipdb & {command}"
192
+ if "pudb" in command: command = f"pip install pudb & {command}"
144
193
  command = fr"""start cmd -Argument "/k %USERPROFILE%\venvs\{args.ve}\Scripts\activate.bat & {command} " """ # this works from powershell
145
194
  # this works from cmd # command = fr""" start cmd /k "%USERPROFILE%\venvs\{args.ve}\Scripts\activate.bat & {command} " """ # because start in cmd is different from start in powershell (in powershell it is short for Start-Process)
146
195
 
@@ -149,13 +198,24 @@ print_code(code=r'''{txt}''', lexer='python', desc='Imported Script')
149
198
  . activate_ve {args.ve}
150
199
  python -m crocodile.cluster.templates.cli_click --file {choice_file} """
151
200
  if choice_function is not None: command += f"--function {choice_function} "
152
- try: tb.install_n_import("clipboard").copy(command)
201
+ try: install_n_import("clipboard").copy(command)
153
202
  except Exception as ex: print(f"Failed to copy command to clipboard. {ex}")
154
203
 
155
204
  if args.loop:
156
205
  command = command + f"\n" + f". {PROGRAM_PATH}"
157
206
 
207
+ if args.Nprocess > 1:
208
+ lines = [f""" zellij action new-tab --name nProcess{randstr(2)}"""]
209
+ command = command.replace(". activate_ve", ". ~/scripts/activate_ve")
210
+ for an_arg in range(args.Nprocess):
211
+ sub_command = f"{command} --idx={an_arg} --idx_max={args.Nprocess}"
212
+ sub_command_path = P.tmpfile(suffix=".sh").write_text(sub_command)
213
+ lines.append(f"""zellij action new-pane -- bash {sub_command_path} """)
214
+ lines.append("sleep 1") # python tends to freeze if you launch instances within 1 microsecond of each other
215
+ command = "\n".join(lines)
216
+
158
217
  # TODO: send this command to terminal history. In powershell & bash there is no way to do it with a command other than goiing to history file. In Mcfly there is a way but its linux only tool. # if platform.system() == "Windows": command = f" ({command}) | Add-History -PassThru "
218
+ # mcfly add --exit 0 command
159
219
  print(f"🔥 command:\n{command}\n\n")
160
220
  # if platform.system() == "Linux":
161
221
  # command = "timeout 1s aafire -driver slang\nclear\n" + command
@@ -169,14 +229,14 @@ def parse_pyfile(file_path: str):
169
229
  func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
170
230
 
171
231
  import ast
172
- parsed_ast = ast.parse(tb.P(file_path).read_text(encoding='utf-8'))
232
+ parsed_ast = ast.parse(P(file_path).read_text(encoding='utf-8'))
173
233
  functions = [
174
234
  node
175
235
  for node in ast.walk(parsed_ast)
176
236
  if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
177
237
  ]
178
238
  module__doc__ = ast.get_docstring(parsed_ast)
179
- main_option = f"RUN AS MAIN -- {tb.Display.get_repr(module__doc__, limit=150) if module__doc__ is not None else 'NoDocs'}"
239
+ main_option = f"RUN AS MAIN -- {Display.get_repr(module__doc__, limit=150) if module__doc__ is not None else 'NoDocs'}"
180
240
  options = [main_option]
181
241
  for function in functions:
182
242
  if function.name.startswith('__') and function.name.endswith('__'): continue
@@ -192,7 +252,7 @@ def parse_pyfile(file_path: str):
192
252
  except KeyError as ke:
193
253
  # type_ = arg.annotation.__name__
194
254
  # print(f"Failed to get type for {arg.annotation}. {ke}")
195
- # tb.Struct(get_attrs(arg.annotation)).print(as_yaml=True)
255
+ # Struct(get_attrs(arg.annotation)).print(as_yaml=True)
196
256
  type_ = "Any" # e.g. a callable object
197
257
  _ = ke
198
258
  # raise ke
@@ -207,11 +267,11 @@ def parse_pyfile(file_path: str):
207
267
  return options, func_args
208
268
 
209
269
 
210
- def get_attrs(obj: Any):
270
+ def get_attrs_recursively(obj: Any):
211
271
  if hasattr(obj, '__dict__'):
212
272
  res = {}
213
273
  for k, v in obj.__dict__.items():
214
- res[k] = get_attrs(v)
274
+ res[k] = get_attrs_recursively(v)
215
275
  return res
216
276
  return obj
217
277
 
@@ -253,28 +313,78 @@ def run_on_remote(func_file: str, args: argparse.Namespace):
253
313
  m.run()
254
314
 
255
315
 
256
- def find_root_path(start_path: str):
316
+ def find_repo_root_path(start_path: str) -> Optional[str]:
257
317
  root_files = ['setup.py', 'pyproject.toml', '.git']
258
- path = start_path
259
- while path != '/':
318
+ path: str = start_path
319
+ trials = 0
320
+ root_path = os.path.abspath(os.sep)
321
+ while path != root_path and trials < 20:
260
322
  for root_file in root_files:
261
323
  if os.path.exists(os.path.join(path, root_file)):
324
+ print(f"Found repo root path: {path}")
262
325
  return path
263
326
  path = os.path.dirname(path)
327
+ trials += 1
264
328
  return None
265
329
 
266
330
 
267
331
  def get_import_module_code(module_path: str):
268
- root_path = find_root_path(module_path)
332
+ root_path = find_repo_root_path(module_path)
269
333
  if root_path is None: # just make a desperate attempt to import it
270
334
  module_name = module_path.lstrip(os.sep).replace(os.sep, '.').replace('.py', '')
271
- return f"from {module_path} import *"
272
- relative_path = module_path.replace(root_path, '')
273
- module_name = relative_path.lstrip(os.sep).replace(os.sep, '.').replace('.py', '')
274
- module_name = module_name.replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("resources.", "").replace("source.", "")
335
+ else:
336
+ relative_path = module_path.replace(root_path, '')
337
+ module_name = relative_path.lstrip(os.sep).replace(os.sep, '.').replace('.py', '')
338
+ module_name = module_name.replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "").replace("src.", "").replace("resources.", "").replace("source.", "")
339
+ if any(char in module_name for char in "- :/\\"):
340
+ module_name = "IncorrectModuleName"
341
+ # TODO: use py_compile to check if the statement is valid code to avoid syntax errors that can't be caught.
275
342
  return f"from {module_name} import *"
276
343
 
277
344
 
345
+ def get_jupyter_notebook(python_code: str):
346
+ template = """
347
+ {
348
+ "cells": [
349
+ {
350
+ "cell_type": "code",
351
+ "execution_count": 1,
352
+ "id": "7412902a-3074-475b-9820-71b82e670a2a",
353
+ "metadata": {},
354
+ "outputs": [],
355
+ "source": [
356
+ "\n",
357
+ "import math"
358
+ ]
359
+ }
360
+ ],
361
+ "metadata": {
362
+ "kernelspec": {
363
+ "display_name": "Python 3 (ipykernel)",
364
+ "language": "python",
365
+ "name": "python3"
366
+ },
367
+ "language_info": {
368
+ "codemirror_mode": {
369
+ "name": "ipython",
370
+ "version": 3
371
+ },
372
+ "file_extension": ".py",
373
+ "mimetype": "text/x-python",
374
+ "name": "python",
375
+ "nbconvert_exporter": "python",
376
+ "pygments_lexer": "ipython3",
377
+ "version": "3.11.7"
378
+ }
379
+ },
380
+ "nbformat": 4,
381
+ "nbformat_minor": 5
382
+ }
383
+ """
384
+ template.replace('"import math"', python_code)
385
+ return template
386
+
387
+
278
388
  if __name__ == '__main__':
279
389
  # options, func_args = parse_pyfile(file_path="C:/Users/aalsaf01/code/crocodile/myresources/crocodile/core.py")
280
390
  main()
@@ -3,6 +3,7 @@
3
3
 
4
4
  TODO: add support for cases in which source or target has non 22 default port number and is defineda as user@host:port:path which makes 2 colons in the string.
5
5
  Currently, the only way to work around this is to predifine the host in ~/.ssh/config and use the alias in the source or target which is inconvenient when dealing with newly setup machines.
6
+
6
7
  """
7
8
 
8
9
  import argparse
@@ -19,6 +20,7 @@ def main():
19
20
  # FLAGS
20
21
  parser.add_argument("--recursive", "-r", help="Send recursively.", action="store_true") # default is False
21
22
  parser.add_argument("--zipFirst", "-z", help="Zip before sending.", action="store_true") # default is False
23
+ parser.add_argument("--cloud", "-c", help="Transfer through the cloud.", action="store_true") # default is False
22
24
 
23
25
  args = parser.parse_args()
24
26
 
@@ -54,6 +56,7 @@ def main():
54
56
  raise ValueError("Either source or target must be a remote path (i.e. machine:path)")
55
57
 
56
58
  Struct({"source": str(source), "target": str(target), "machine": machine}).print(as_config=True, title="CLI Resolution")
59
+
57
60
  from paramiko.ssh_exception import AuthenticationException # type: ignore
58
61
  try:
59
62
  ssh = SSH(rf'{machine}')
@@ -64,17 +67,24 @@ def main():
64
67
  pwd = getpass.getpass()
65
68
  ssh = SSH(rf'{machine}', pwd=pwd)
66
69
 
67
- if source_is_remote:
68
- assert source is not None, "source must be a remote path (i.e. machine:path)"
69
- print(f"Running: received_file = ssh.copy_to_here(source={source}, target={target}, z={args.zipFirst}, r={args.recursive})")
70
- received_file = ssh.copy_to_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
70
+ if args.cloud:
71
+ print("Uploading from remote to cloud ...")
72
+ ssh.run(f"cloud_copy {source} :^", desc="Uploading from remote to the cloud.").print()
73
+ print("Downloading from cloud to local ...")
74
+ ssh.run_locally(f"cloud_copy :^ {target}").print()
75
+ received_file = P(target) # type: ignore
71
76
  else:
72
- assert source is not None, "target must be a remote path (i.e. machine:path)"
73
- print(f"Running: received_file = ssh.copy_from_here(source={source}, target={target}, z={args.zipFirst}, r={args.recursive})")
74
- received_file = ssh.copy_from_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
77
+ if source_is_remote:
78
+ assert source is not None, "source must be a remote path (i.e. machine:path)"
79
+ print(f"Running: received_file = ssh.copy_to_here(source=r'{source}', target=r'{target}', z={args.zipFirst}, r={args.recursive})")
80
+ received_file = ssh.copy_to_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
81
+ else:
82
+ assert source is not None, "target must be a remote path (i.e. machine:path)"
83
+ print(f"Running: received_file = ssh.copy_from_here(source=r'{source}', target=r'{target}', z={args.zipFirst}, r={args.recursive})")
84
+ received_file = ssh.copy_from_here(source=source, target=target, z=args.zipFirst, r=args.recursive)
75
85
  # ssh.print_summary()
76
86
  # if P(args.file).is_dir(): print(f"Use: cd {repr(P(args.file).expanduser())}")
77
- if isinstance(received_file, P):
87
+ if source_is_remote and isinstance(received_file, P):
78
88
  print(f"Received: {repr(received_file.parent), repr(received_file)}")
79
89
 
80
90
 
@@ -2,7 +2,10 @@
2
2
  """NFS mounting script
3
3
  """
4
4
 
5
- import crocodile.toolbox as tb
5
+
6
+ from crocodile.file_management import P
7
+ from crocodile.meta import SSH, Terminal
8
+ from crocodile.core import List as L
6
9
  from machineconfig.utils.utils import display_options, PROGRAM_PATH, choose_ssh_host
7
10
  import platform
8
11
 
@@ -13,22 +16,22 @@ def main():
13
16
  if share_info == "":
14
17
  tmp = choose_ssh_host(multi=False)
15
18
  assert isinstance(tmp, str)
16
- ssh = tb.SSH(tmp)
19
+ ssh = SSH(tmp)
17
20
  default = f"{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_nfs"
18
- share_info = display_options("Choose a share path: ", options=tb.L(ssh.run("cat /etc/exports").op.split("\n")).filter(lambda x: not x.startswith("#")).apply(lambda x: f"{ssh.hostname}:{x.split(' ')[0]}").list + [default], default=default)
21
+ share_info = display_options("Choose a share path: ", options=L(ssh.run("cat /etc/exports").op.split("\n")).filter(lambda x: not x.startswith("#")).apply(lambda x: f"{ssh.hostname}:{x.split(' ')[0]}").list + [default], default=default)
19
22
  assert isinstance(share_info, str), f"share_info must be a string. Got {type(share_info)}"
20
23
  remote_server = share_info.split(":")[0]
21
24
  share_path = share_info.split(":")[1]
22
25
 
23
26
  if platform.system() == "Linux":
24
- mount_path_1 = tb.P(share_path)
27
+ mount_path_1 = P(share_path)
25
28
  print(remote_server)
26
- mount_path_2 = tb.P.home().joinpath(f"data/mount_nfs/{remote_server}")
27
- if str(mount_path_1).startswith("/home"): mount_path_3 = tb.P.home().joinpath(*mount_path_1.parts[3:])
29
+ mount_path_2 = P.home().joinpath(f"data/mount_nfs/{remote_server}")
30
+ if str(mount_path_1).startswith("/home"): mount_path_3 = P.home().joinpath(*mount_path_1.parts[3:])
28
31
  else: mount_path_3 = mount_path_2
29
32
  local_mount_point = display_options(msg="choose mount path OR input custom one", options=[mount_path_1, mount_path_2, mount_path_3], default=mount_path_2, custom_input=True)
30
- assert isinstance(local_mount_point, tb.P), f"local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
31
- local_mount_point = tb.P(local_mount_point).expanduser()
33
+ assert isinstance(local_mount_point, P), f"local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
34
+ local_mount_point = P(local_mount_point).expanduser()
32
35
  PROGRAM_PATH.write_text(f"""
33
36
  share_info={share_info}
34
37
  remote_server={remote_server}
@@ -36,14 +39,14 @@ share_path={share_path}
36
39
  local_mount_point={local_mount_point}
37
40
  """)
38
41
  elif platform.system() == "Windows":
39
- print(tb.Terminal().run("Get-PSDrive -PSProvider 'FileSystem'", shell="powershell").op)
42
+ print(Terminal().run("Get-PSDrive -PSProvider 'FileSystem'", shell="powershell").op)
40
43
  driver_letter = input(r"Choose driver letter (e.g. Z:\) (avoid the ones already used) : ") or "Z:\\"
41
44
  PROGRAM_PATH.write_text(f"""
42
45
  $server = "{remote_server}"
43
46
  $sharePath = "{share_path}"
44
47
  $driveLetter = "{driver_letter}"
45
48
  """)
46
- # tb.P.home().joinpath(f"data/mount_nfs/{remote_server}").symlink_to(target="") # can't be created until the mount is finished
49
+ # P.home().joinpath(f"data/mount_nfs/{remote_server}").symlink_to(target="") # can't be created until the mount is finished
47
50
  print(PROGRAM_PATH.read_text())
48
51
 
49
52
 
@@ -1,6 +1,7 @@
1
1
 
2
- import crocodile.toolbox as tb
2
+
3
3
  from machineconfig.utils.utils import PROGRAM_PATH
4
+ from crocodile.file_management import P
4
5
  import platform
5
6
 
6
7
 
@@ -9,8 +10,8 @@ def main():
9
10
  drive_location = input("Enter the network drive location (ex: //192.168.1.100/Share): ")
10
11
  machine_name = drive_location.split("//")[1].split("/")[0]
11
12
  mount_point = input(f"Enter the mount point directory (ex: /mnt/network) [default: ~/data/mount_nw/{machine_name}]: ")
12
- if mount_point == "": mount_point = tb.P.home().joinpath(fr"data/mount_nw/{machine_name}")
13
- else: mount_point = tb.P(mount_point).expanduser()
13
+ if mount_point == "": mount_point = P.home().joinpath(fr"data/mount_nw/{machine_name}")
14
+ else: mount_point = P(mount_point).expanduser()
14
15
 
15
16
  username = input(f"Enter the username: ")
16
17
  password = input(f"Enter the password: ")
@@ -2,8 +2,11 @@
2
2
  """Mount a remote SSHFS share on a local directory
3
3
  """
4
4
 
5
- import crocodile.toolbox as tb
5
+
6
6
  from platform import system
7
+ from crocodile.meta import SSH, Terminal
8
+ from crocodile.file_management import P
9
+
7
10
  from machineconfig.utils.utils import PROGRAM_PATH, choose_ssh_host
8
11
 
9
12
 
@@ -14,15 +17,15 @@ def main():
14
17
  if share_info == "":
15
18
  tmp = choose_ssh_host(multi=False)
16
19
  assert isinstance(tmp, str)
17
- ssh = tb.SSH(host=tmp)
20
+ ssh = SSH(host=tmp)
18
21
  share_info = f"{ssh.username}@{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_ssh"
19
22
  else:
20
- ssh = tb.SSH(share_info.split(":")[0])
21
- print(tb.Terminal().run("net use", shell="powershell").op)
23
+ ssh = SSH(share_info.split(":")[0])
24
+ print(Terminal().run("net use", shell="powershell").op)
22
25
  driver_letter = input(r"Choose driver letter (e.g. Z:\) (avoid the ones already used) : ") or "Z:\\"
23
26
 
24
27
  mount_point = input(f"Enter the mount point directory (ex: /mnt/network) [default: ~/data/mount_ssh/{ssh.hostname}]: ")
25
- if mount_point == "": mount_point = tb.P.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
28
+ if mount_point == "": mount_point = P.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
26
29
 
27
30
  if system() == "Linux":
28
31
  txt = fr"""