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
@@ -4,10 +4,10 @@ from pydantic import ConfigDict
4
4
  from pydantic.dataclasses import dataclass
5
5
  from typing import Optional
6
6
  import os
7
- from machineconfig.utils.utils import DEFAULTS_PATH
7
+ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
- from rich import box # Import box
10
+ from rich import box # Import box
11
11
 
12
12
 
13
13
  console = Console()
@@ -16,10 +16,10 @@ console = Console()
16
16
  class ArgsDefaults:
17
17
  # source: str=None
18
18
  # target: str=None
19
- encrypt: bool=False
20
- zip_: bool=False
21
- overwrite: bool=False
22
- share: bool=False
19
+ encrypt: bool = False
20
+ zip_: bool = False
21
+ overwrite: bool = False
22
+ share: bool = False
23
23
  rel2home = False
24
24
  root = None
25
25
  os_specific = False
@@ -28,12 +28,12 @@ class ArgsDefaults:
28
28
 
29
29
 
30
30
  @dataclass(config=ConfigDict(extra="forbid", frozen=False))
31
- class Args():
31
+ class Args:
32
32
  cloud: Optional[str] = None
33
33
 
34
- zip: bool=ArgsDefaults.zip_
35
- overwrite: bool=ArgsDefaults.overwrite
36
- share: bool=ArgsDefaults.share
34
+ zip: bool = ArgsDefaults.zip_
35
+ overwrite: bool = ArgsDefaults.overwrite
36
+ share: bool = ArgsDefaults.share
37
37
 
38
38
  root: Optional[str] = ArgsDefaults.root
39
39
  os_specific: bool = ArgsDefaults.os_specific
@@ -67,15 +67,16 @@ def find_cloud_config(path: Path):
67
67
 
68
68
  def absolute(path: str) -> Path:
69
69
  obj = Path(path).expanduser()
70
- if not path.startswith(".") and obj.exists(): return obj
71
- try_absing = Path.cwd().joinpath(path)
72
- if try_absing.exists(): return try_absing
70
+ if not path.startswith(".") and obj.exists():
71
+ return obj
72
+ try_absing = Path.cwd().joinpath(path)
73
+ if try_absing.exists():
74
+ return try_absing
73
75
  display_warning(f"Path {path} could not be resolved to absolute path.")
74
76
  display_warning("Trying to resolve symlinks (this may result in unintended paths).")
75
77
  return obj.absolute()
76
78
 
77
79
 
78
-
79
80
  def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Args:
80
81
  console = Console()
81
82
  console.print(Panel("🔐 Secure Share Cloud Configuration", expand=False))
@@ -88,10 +89,10 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
88
89
  console.print(f"☁️ Using cloud from environment: {cloud}")
89
90
  else:
90
91
  try:
91
- default_cloud__ = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
92
+ default_cloud__ = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
92
93
  except Exception:
93
- default_cloud__ = 'No default cloud found.'
94
- if default_cloud__ == 'No default cloud found.' or interactive:
94
+ default_cloud__ = "No default cloud found."
95
+ if default_cloud__ == "No default cloud found." or interactive:
95
96
  # assert default_cloud is not None
96
97
  cloud = input(f"☁️ Enter cloud name (default {default_cloud__}): ") or default_cloud__
97
98
  else:
@@ -106,32 +107,36 @@ def get_secure_share_cloud_config(interactive: bool, cloud: Optional[str]) -> Ar
106
107
  pwd = ""
107
108
  default_message = "no default password found"
108
109
  pwd = input(f"🔑 Enter encryption password ({default_message}): ") or pwd
109
- res = Args(cloud=cloud,
110
- pwd=pwd, encrypt=True,
111
- zip=True, overwrite=True, share=True,
112
- rel2home=True, root="myshare", os_specific=False,)
110
+ res = Args(cloud=cloud, pwd=pwd, encrypt=True, zip=True, overwrite=True, share=True, rel2home=True, root="myshare", os_specific=False)
113
111
 
114
112
  display_success("Using SecureShare cloud config")
115
113
  pprint(res.__dict__, "SecureShare Config")
116
114
  return res
117
115
 
116
+
118
117
  def display_header(title: str):
119
- console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left")) # Replace print with Panel
118
+ console.print(Panel(title, box=box.DOUBLE_EDGE, title_align="left")) # Replace print with Panel
119
+
120
120
 
121
121
  def display_subheader(title: str):
122
- console.print(Panel(title, box=box.ROUNDED, title_align="left")) # Replace print with Panel
122
+ console.print(Panel(title, box=box.ROUNDED, title_align="left")) # Replace print with Panel
123
+
123
124
 
124
125
  def display_content(content: str):
125
- console.print(Panel(content, box=box.ROUNDED, title_align="left")) # Replace print with Panel
126
+ console.print(Panel(content, box=box.ROUNDED, title_align="left")) # Replace print with Panel
127
+
126
128
 
127
129
  def display_status(status: str):
128
- console.print(Panel(status, box=box.ROUNDED, title_align="left")) # Replace print with Panel
130
+ console.print(Panel(status, box=box.ROUNDED, title_align="left")) # Replace print with Panel
131
+
129
132
 
130
133
  def display_success(message: str):
131
- console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left")) # Replace print with Panel
134
+ console.print(Panel(message, box=box.ROUNDED, border_style="green", title_align="left")) # Replace print with Panel
135
+
132
136
 
133
137
  def display_warning(message: str):
134
- console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left")) # Replace print with Panel
138
+ console.print(Panel(message, box=box.ROUNDED, border_style="yellow", title_align="left")) # Replace print with Panel
139
+
135
140
 
136
141
  def display_error(message: str):
137
- console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left")) # Replace print with Panel
142
+ console.print(Panel(message, box=box.ROUNDED, border_style="red", title_align="left")) # Replace print with Panel
@@ -1,5 +1,5 @@
1
1
  from machineconfig.scripts.python.helpers.cloud_helpers import Args, ArgsDefaults, absolute, find_cloud_config, get_secure_share_cloud_config
2
- from machineconfig.utils.utils import DEFAULTS_PATH
2
+ from machineconfig.utils.source_of_truth import DEFAULTS_PATH
3
3
  from machineconfig.utils.utils2 import read_ini, pprint
4
4
  from typing import Optional
5
5
  from rich.console import Console
@@ -17,7 +17,8 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
17
17
  if config == "ss":
18
18
  cloud_maybe: Optional[str] = target.split(":")[0]
19
19
  # if cloud_maybe == "": cloud_maybe = source.split(":")[0]
20
- if cloud_maybe == "": cloud_maybe = None
20
+ if cloud_maybe == "":
21
+ cloud_maybe = None
21
22
  print("cloud_maybe:", cloud_maybe)
22
23
  maybe_config = get_secure_share_cloud_config(interactive=True, cloud=cloud_maybe)
23
24
  elif config is not None:
@@ -27,13 +28,20 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
27
28
  maybe_config = None
28
29
 
29
30
  if maybe_config is not None:
30
- if args.zip == ArgsDefaults.zip_: args.zip = maybe_config.zip
31
- if args.encrypt == ArgsDefaults.encrypt: args.encrypt = maybe_config.encrypt
32
- if args.share == ArgsDefaults.share: args.share = maybe_config.share
33
- if args.root == ArgsDefaults.root: args.root = maybe_config.root
34
- if args.rel2home == ArgsDefaults.rel2home: args.rel2home = maybe_config.rel2home
35
- if args.pwd == ArgsDefaults.pwd: args.pwd = maybe_config.pwd
36
- if args.os_specific == ArgsDefaults.os_specific: args.os_specific = maybe_config.os_specific
31
+ if args.zip == ArgsDefaults.zip_:
32
+ args.zip = maybe_config.zip
33
+ if args.encrypt == ArgsDefaults.encrypt:
34
+ args.encrypt = maybe_config.encrypt
35
+ if args.share == ArgsDefaults.share:
36
+ args.share = maybe_config.share
37
+ if args.root == ArgsDefaults.root:
38
+ args.root = maybe_config.root
39
+ if args.rel2home == ArgsDefaults.rel2home:
40
+ args.rel2home = maybe_config.rel2home
41
+ if args.pwd == ArgsDefaults.pwd:
42
+ args.pwd = maybe_config.pwd
43
+ if args.os_specific == ArgsDefaults.os_specific:
44
+ args.os_specific = maybe_config.os_specific
37
45
 
38
46
  root = args.root
39
47
  rel2home = args.rel2home
@@ -53,7 +61,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
53
61
  maybe_config = tmp_maybe_config
54
62
 
55
63
  if maybe_config is None:
56
- default_cloud: str=read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
64
+ default_cloud: str = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
57
65
  console.print(Panel(f"⚠️ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
58
66
  source = default_cloud + ":" + source[1:]
59
67
  else:
@@ -73,7 +81,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
73
81
  maybe_config = find_cloud_config(path)
74
82
 
75
83
  if maybe_config is None:
76
- default_cloud = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
84
+ default_cloud = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
77
85
  console.print(Panel(f"⚠️ No cloud config found. Using default cloud: {default_cloud}", width=150, border_style="yellow"))
78
86
  target = default_cloud + ":" + target[1:]
79
87
  print("target mutated to:", target, f"because of default cloud being {default_cloud}")
@@ -88,7 +96,6 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
88
96
  zip_arg = tmp.zip
89
97
  share = tmp.share
90
98
 
91
-
92
99
  if ":" in source and (source[1] != ":" if len(source) > 1 else True): # avoid the deceptive case of "C:/"
93
100
  source_parts: list[str] = source.split(":")
94
101
  cloud = source_parts[0]
@@ -97,6 +104,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
97
104
  assert ES not in target, f"You can't use expand symbol `{ES}` in both source and target. Cyclical inference dependency arised."
98
105
  target_obj = absolute(target)
99
106
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
107
+
100
108
  remote_path = PathExtended(target_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
101
109
  source = f"{cloud}:{remote_path.as_posix()}"
102
110
  else: # source path is mentioned, target? maybe.
@@ -116,6 +124,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
116
124
  assert ES not in source, "You can't use $ in both source and target. Cyclical inference dependency arised."
117
125
  source_obj = absolute(source)
118
126
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
127
+
119
128
  remote_path = PathExtended(source_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
120
129
  target = f"{cloud}:{remote_path.as_posix()}"
121
130
  else: # target path is mentioned, source? maybe.
@@ -124,8 +133,10 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
124
133
  raise NotImplementedError("There is no .get_local_path method yet")
125
134
  else:
126
135
  source_obj = absolute(source)
127
- if zip_arg and ".zip" not in target: target += ".zip"
128
- if encrypt and ".enc" not in target: target += ".enc"
136
+ if zip_arg and ".zip" not in target:
137
+ target += ".zip"
138
+ if encrypt and ".enc" not in target:
139
+ target += ".enc"
129
140
  else:
130
141
  console.print(Panel("❌ ERROR: Invalid path configuration\nEither source or target must be a remote path (i.e. machine:path)", title="[bold red]Error[/bold red]", border_style="red"))
131
142
  raise ValueError(f"Either source or target must be a remote path (i.e. machine:path)\nGot: source: `{source}`, target: `{target}`")
@@ -1,7 +1,7 @@
1
-
2
1
  from typing import Any, Callable, Optional
3
2
  import inspect
4
3
  import os
4
+
5
5
  # import argparse
6
6
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
7
  # from machineconfig.utils.utils import choose_ssh_host
@@ -27,7 +27,7 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
27
27
  # https://google.github.io/python-fire/guide/
28
28
  # https://github.com/google/python-fire/blob/master/docs/guide.md#argument-parsing
29
29
  if not kwargs: # empty dict
30
- kwargs_str = ''
30
+ kwargs_str = ""
31
31
  else:
32
32
  # For fire module, all keyword arguments should be passed as --key value pairs
33
33
  tmp_list: list[str] = []
@@ -40,40 +40,48 @@ def convert_kwargs_to_fire_kwargs_str(kwargs: dict[str, Any]) -> str:
40
40
  def parse_pyfile(file_path: str):
41
41
  print(f"🔍 Loading {file_path} ...")
42
42
  from typing import NamedTuple
43
+
43
44
  args_spec = NamedTuple("args_spec", [("name", str), ("type", str), ("default", Optional[str])])
44
45
  func_args: list[list[args_spec]] = [[]] # this firt prepopulated dict is for the option 'RUN AS MAIN' which has no args
45
46
 
46
47
  import ast
47
- parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding='utf-8'))
48
- functions = [
49
- node
50
- for node in ast.walk(parsed_ast)
51
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
52
- ]
48
+
49
+ parsed_ast = ast.parse(PathExtended(file_path).read_text(encoding="utf-8"))
50
+ functions = [node for node in ast.walk(parsed_ast) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
53
51
  module__doc__ = ast.get_docstring(parsed_ast)
54
52
  main_option = f"RUN AS MAIN -- {module__doc__ if module__doc__ is not None else 'NoDocs'}"
55
53
  options = [main_option]
56
54
  for function in functions:
57
- if function.name.startswith('__') and function.name.endswith('__'): continue
58
- if any(arg.arg == 'self' for arg in function.args.args): continue
59
- if any(arg.arg == 'self' for arg in function.args.args): continue
55
+ if function.name.startswith("__") and function.name.endswith("__"):
56
+ continue
57
+ if any(arg.arg == "self" for arg in function.args.args):
58
+ continue
59
+ if any(arg.arg == "self" for arg in function.args.args):
60
+ continue
60
61
  doc_string_tmp: str | None = ast.get_docstring(function)
61
- if doc_string_tmp is None: doc_string = "NoDocs"
62
- else: doc_string = doc_string_tmp.replace('\n', ' ')
62
+ if doc_string_tmp is None:
63
+ doc_string = "NoDocs"
64
+ else:
65
+ doc_string = doc_string_tmp.replace("\n", " ")
63
66
  options.append(f"{function.name} -- {', '.join([arg.arg for arg in function.args.args])} -- {doc_string}")
64
67
  tmp = []
65
68
  for idx, arg in enumerate(function.args.args):
66
69
  if arg.annotation is not None:
67
- try: type_ = arg.annotation.__dict__['id']
70
+ try:
71
+ type_ = arg.annotation.__dict__["id"]
68
72
  except KeyError as ke:
69
73
  type_ = "Any" # e.g. a callable object
70
74
  _ = ke
71
- else: type_ = "Any"
75
+ else:
76
+ type_ = "Any"
72
77
  default_tmp = function.args.defaults[idx] if idx < len(function.args.defaults) else None
73
- if default_tmp is None: default = None
78
+ if default_tmp is None:
79
+ default = None
74
80
  else:
75
- if hasattr(default_tmp, "__dict__"): default = default_tmp.__dict__.get("value", None)
76
- else: default = None
81
+ if hasattr(default_tmp, "__dict__"):
82
+ default = default_tmp.__dict__.get("value", None)
83
+ else:
84
+ default = None
77
85
  tmp.append(args_spec(name=arg.arg, type=type_, default=default))
78
86
  func_args.append(tmp)
79
87
  return options, func_args
@@ -85,26 +93,32 @@ def interactively_run_function(func: Callable[[Any], Any]):
85
93
  args = []
86
94
  kwargs = {}
87
95
  for param in params:
88
- if param.annotation is not inspect.Parameter.empty: hint = f" ({param.annotation.__name__})"
89
- else: hint = ""
96
+ if param.annotation is not inspect.Parameter.empty:
97
+ hint = f" ({param.annotation.__name__})"
98
+ else:
99
+ hint = ""
90
100
  if param.default is not inspect.Parameter.empty:
91
101
  default = param.default
92
102
  value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) (default = {default}) : ")
93
- if value == "": value = default
94
- else: value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
103
+ if value == "":
104
+ value = default
105
+ else:
106
+ value = input(f"Please enter a value for argument `{param.name}` (type = {hint}) : ")
95
107
  try:
96
108
  if param.annotation is not inspect.Parameter.empty:
97
109
  value = param.annotation
98
110
  except (TypeError, ValueError) as err:
99
111
  raise ValueError(f"Invalid input: {value} is not of type {param.annotation}") from err
100
- if param.kind == inspect.Parameter.KEYWORD_ONLY: kwargs[param.name] = value
101
- else: args.append((param.name, value))
112
+ if param.kind == inspect.Parameter.KEYWORD_ONLY:
113
+ kwargs[param.name] = value
114
+ else:
115
+ args.append((param.name, value))
102
116
  args_to_kwargs = dict(args)
103
117
  return args_to_kwargs, kwargs
104
118
 
105
119
 
106
120
  def get_attrs_recursively(obj: Any):
107
- if hasattr(obj, '__dict__'):
121
+ if hasattr(obj, "__dict__"):
108
122
  res = {}
109
123
  for k, v in obj.__dict__.items():
110
124
  res[k] = get_attrs_recursively(v)
@@ -127,8 +141,8 @@ def get_attrs_recursively(obj: Any):
127
141
 
128
142
 
129
143
  def find_repo_root_path(start_path: str) -> Optional[str]:
130
- root_files = ['setup.py', 'pyproject.toml', '.git']
131
- path: str=start_path
144
+ root_files = ["setup.py", "pyproject.toml", ".git"]
145
+ path: str = start_path
132
146
  trials = 0
133
147
  root_path = os.path.abspath(os.sep)
134
148
  while path != root_path and trials < 20:
@@ -144,12 +158,12 @@ def find_repo_root_path(start_path: str) -> Optional[str]:
144
158
  def get_import_module_code(module_path: str):
145
159
  root_path = find_repo_root_path(module_path)
146
160
  if root_path is None: # just make a desperate attempt to import it
147
- module_name = module_path.lstrip(os.sep).replace(os.sep, '.')
161
+ module_name = module_path.lstrip(os.sep).replace(os.sep, ".")
148
162
  if module_name.endswith(".py"):
149
163
  module_name = module_name[:-3]
150
164
  else:
151
- relative_path = module_path.replace(root_path, '')
152
- module_name = relative_path.lstrip(os.sep).replace(os.sep, '.')
165
+ relative_path = module_path.replace(root_path, "")
166
+ module_name = relative_path.lstrip(os.sep).replace(os.sep, ".")
153
167
  if module_name.endswith(".py"):
154
168
  module_name = module_name[:-3]
155
169
  # module_name = module_name.replace("src.", "").replace("myresources.", "").replace("resources.", "").replace("source.", "")
@@ -47,4 +47,4 @@ def get_jupyter_notebook(python_code: str):
47
47
  # nb: NotebookNode = nbf.v4.new_notebook()
48
48
  # nb.cells.append(nbf.v4.new_code_cell(py_script))
49
49
  # with open("new_notebook.ipynb", mode="w", encoding="utf-8") as f:
50
- # nbf.write(nb,f)
50
+ # nbf.write(nb,f)
@@ -1,7 +1,7 @@
1
1
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
2
  from machineconfig.utils.terminal import Terminal
3
3
  from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
4
- from machineconfig.utils.utils import CONFIG_PATH, DEFAULTS_PATH
4
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
5
5
  from machineconfig.utils.utils2 import read_ini
6
6
  from machineconfig.utils.code import write_shell_script_to_file
7
7
  import platform
@@ -19,10 +19,12 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
19
19
  print("🧹 Removed temporary remote copy")
20
20
  from git.remote import Remote
21
21
  from git.repo import Repo
22
+
22
23
  try:
23
24
  Remote.remove(Repo(repo_root_path), "originEnc")
24
25
  console.print(Panel("🔗 Removed originEnc remote reference", border_style="blue"))
25
- except Exception: pass # type: ignore
26
+ except Exception:
27
+ pass # type: ignore
26
28
  console.print(Panel("📈 Deleting remote repository copy and pushing local changes", width=150, border_style="blue"))
27
29
 
28
30
  repo_root_path.to_cloud(cloud=cloud, zip=True, encrypt=True, rel2home=True, os_specific=False)
@@ -37,7 +39,7 @@ def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cl
37
39
  def get_wt_cmd(wd1: PathExtended, wd2: PathExtended) -> str:
38
40
  lines = [
39
41
  f"""wt --window 0 new-tab --profile pwsh --title "gitdiff" --tabColor `#3b04d1 --startingDirectory {wd1} ` --colorScheme "Solarized Dark" """,
40
- f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """
42
+ f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """,
41
43
  ]
42
44
  return " `; ".join(lines)
43
45
 
@@ -53,24 +55,24 @@ def inspect_repos(repo_local_root: str, repo_remote_root: str):
53
55
  program = get_zellij_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_remote_root))
54
56
  write_shell_script_to_file(shell_script=program)
55
57
  return None
56
- else: raise NotImplementedError(f"Platform {platform.system()} not implemented.")
58
+ else:
59
+ raise NotImplementedError(f"Platform {platform.system()} not implemented.")
57
60
 
58
61
 
59
62
  def fetch_dotfiles():
60
63
  console.print(Panel("📁 Fetching Dotfiles", title="[bold blue]Dotfiles[/bold blue]", border_style="blue"))
61
64
 
62
- cloud_resolved = read_ini(DEFAULTS_PATH)['general']['rclone_config_name']
65
+ cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
63
66
  console.print(Panel(f"⚠️ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", width=150, border_style="yellow"))
64
67
 
65
68
  dotfiles_local = PathExtended.home().joinpath("dotfiles")
66
69
  CONFIG_PATH.joinpath("remote").mkdir(parents=True, exist_ok=True)
67
- dotfiles_remote = CONFIG_PATH.joinpath("remote", dotfiles_local.rel2home())
70
+ dotfiles_remote = PathExtended(CONFIG_PATH).joinpath("remote", dotfiles_local.rel2home())
68
71
  remote_path = dotfiles_local.get_remote_path(rel2home=True, os_specific=False, root="myhome") + ".zip.enc"
69
72
 
70
73
  console.print(Panel("📥 Downloading dotfiles from cloud...", width=150, border_style="blue"))
71
74
 
72
- dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved,
73
- unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
75
+ dotfiles_remote.from_cloud(remotepath=remote_path, cloud=cloud_resolved, unzip=True, decrypt=True, rel2home=True, os_specific=False, pwd=None)
74
76
 
75
77
  console.print(Panel("🗑️ Removing old dotfiles and replacing with cloud version...", width=150, border_style="blue"))
76
78
 
@@ -80,7 +82,8 @@ def fetch_dotfiles():
80
82
  # rm -rf {dotfiles_local}
81
83
  # mv {dotfiles_remote} {dotfiles_local}
82
84
  """
83
- if platform.system() == "Linux": script += """
85
+ if platform.system() == "Linux":
86
+ script += """
84
87
  sudo chmod 600 $HOME/.ssh/*
85
88
  sudo chmod 700 $HOME/.ssh
86
89
  sudo chmod +x $HOME/dotfiles/scripts/linux -R
@@ -90,3 +93,22 @@ sudo chmod +x $HOME/dotfiles/scripts/linux -R
90
93
 
91
94
  console.print(Panel("✅ Dotfiles successfully fetched and installed", title="[bold green]Dotfiles[/bold green]", border_style="green"))
92
95
 
96
+
97
+ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
98
+ dotfiles_path = str(PathExtended.home().joinpath("dotfiles"))
99
+ from git import Repo
100
+
101
+ repo = Repo(path=dotfiles_path)
102
+ last_commit = repo.head.commit
103
+ dtm = last_commit.committed_datetime
104
+ from datetime import datetime # make it tz unaware
105
+
106
+ dtm = datetime(dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minute, dtm.second)
107
+ res = dtm > datetime.fromisoformat(commit_dtm)
108
+ if res is False and update is True:
109
+ console = Console()
110
+ console.print(Panel(f"🔄 UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
111
+ from machineconfig.scripts.python.cloud_repo_sync import main
112
+
113
+ main(cloud=None, path=dotfiles_path)
114
+ return res
@@ -1,12 +1,13 @@
1
- """NFS mounting script
2
- """
1
+ """NFS mounting script"""
3
2
 
4
3
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
4
  from machineconfig.utils.ssh import SSH
6
5
  from machineconfig.utils.terminal import Terminal
7
- from machineconfig.utils.utils import display_options, PROGRAM_PATH, choose_ssh_host
6
+ from machineconfig.utils.options import display_options, choose_ssh_host
7
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
8
8
  import platform
9
9
 
10
+
10
11
  def main():
11
12
  print("\n" + "=" * 50)
12
13
  print("🚀 Starting NFS Mounting Process")
@@ -19,11 +20,7 @@ def main():
19
20
  assert isinstance(tmp, str)
20
21
  ssh = SSH(tmp)
21
22
  default = f"{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_nfs"
22
- share_info = display_options(
23
- "📂 Choose a share path:",
24
- options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default],
25
- default=default
26
- )
23
+ share_info = display_options("📂 Choose a share path:", options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default], default=default)
27
24
  assert isinstance(share_info, str), f"❌ share_info must be a string. Got {type(share_info)}"
28
25
 
29
26
  remote_server = share_info.split(":")[0]
@@ -41,12 +38,7 @@ def main():
41
38
  mount_path_3 = mount_path_2
42
39
 
43
40
  print("🔧 Preparing mount paths...")
44
- local_mount_point = display_options(
45
- msg="📂 Choose mount path OR input custom one:",
46
- options=[mount_path_1, mount_path_2, mount_path_3],
47
- default=mount_path_2,
48
- custom_input=True
49
- )
41
+ 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)
50
42
  assert isinstance(local_mount_point, PathExtended), f"❌ local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
51
43
  local_mount_point = PathExtended(local_mount_point).expanduser()
52
44
 
@@ -79,5 +71,6 @@ $driveLetter = "{driver_letter}"
79
71
 
80
72
  print("🎉 NFS Mounting Process Completed Successfully!\n")
81
73
 
82
- if __name__ == '__main__':
74
+
75
+ if __name__ == "__main__":
83
76
  main()
@@ -1,7 +1,8 @@
1
- from machineconfig.utils.utils import PROGRAM_PATH
1
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
2
2
  from pathlib import Path
3
3
  import platform
4
4
 
5
+
5
6
  def main():
6
7
  print("\n" + "=" * 50)
7
8
  print("🚀 Welcome to the Windows Network Drive Mounting Wizard")
@@ -12,7 +13,7 @@ def main():
12
13
 
13
14
  mount_point_input = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_nw/{machine_name}]: ")
14
15
  if mount_point_input == "":
15
- mount_point = Path.home().joinpath(fr"data/mount_nw/{machine_name}")
16
+ mount_point = Path.home().joinpath(rf"data/mount_nw/{machine_name}")
16
17
  else:
17
18
  mount_point = Path(mount_point_input).expanduser()
18
19
 
@@ -24,13 +25,16 @@ def main():
24
25
 
25
26
  if platform.system() in ["Linux", "Darwin"]:
26
27
  print("\n🔧 Saving configuration for Linux...")
27
- PROGRAM_PATH.write_text(f"""
28
+ PROGRAM_PATH.write_text(
29
+ f"""
28
30
  drive_location='{drive_location}'
29
31
  mount_point='{mount_point}'
30
32
  username='{username}'
31
33
  password='{password}'
32
34
 
33
- """, encoding="utf-8")
35
+ """,
36
+ encoding="utf-8",
37
+ )
34
38
  print("✅ Configuration saved successfully!\n")
35
39
 
36
40
  elif platform.system() == "Windows":
@@ -39,5 +43,6 @@ password='{password}'
39
43
 
40
44
  print("🎉 Windows Network Drive Mounting Process Completed!\n")
41
45
 
42
- if __name__ == '__main__':
46
+
47
+ if __name__ == "__main__":
43
48
  main()
@@ -1,11 +1,12 @@
1
- """Mount a remote SSHFS share on a local directory
2
- """
1
+ """Mount a remote SSHFS share on a local directory"""
3
2
 
4
3
  from platform import system
5
4
  from machineconfig.utils.ssh import SSH
6
5
  from machineconfig.utils.terminal import Terminal
7
6
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
- from machineconfig.utils.utils import PROGRAM_PATH, choose_ssh_host
7
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
8
+ from machineconfig.utils.options import choose_ssh_host
9
+
9
10
 
10
11
  def main():
11
12
  print("\n" + "=" * 50)
@@ -33,7 +34,7 @@ def main():
33
34
 
34
35
  mount_point = input(f"📂 Enter the mount point directory (e.g., /mnt/network) [Default: ~/data/mount_ssh/{ssh.hostname}]: ")
35
36
  if mount_point == "":
36
- mount_point = PathExtended.home().joinpath(fr"data/mount_ssh/{ssh.hostname}")
37
+ mount_point = PathExtended.home().joinpath(rf"data/mount_ssh/{ssh.hostname}")
37
38
 
38
39
  print(f"\n📁 Mount Point: {mount_point}")
39
40
 
@@ -43,7 +44,7 @@ sshfs alex@:/media/dbhdd /media/dbhdd\
43
44
  """
44
45
  print("\n🔧 Preparing SSHFS mount command for Linux...")
45
46
  elif system() == "Windows":
46
- txt = fr"""
47
+ txt = rf"""
47
48
  net use {driver_letter} {share_info}
48
49
  fusermount -u /mnt/dbhdd
49
50
  """
@@ -56,5 +57,6 @@ fusermount -u /mnt/dbhdd
56
57
 
57
58
  print("🎉 SSHFS Mounting Process Completed!\n")
58
59
 
59
- if __name__ == '__main__':
60
+
61
+ if __name__ == "__main__":
60
62
  main()