abstract-utilities 0.2.2.442__py3-none-any.whl → 0.2.2.688__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 abstract-utilities might be problematic. Click here for more details.

Files changed (236) hide show
  1. abstract_utilities/__init__.py +25 -16
  2. abstract_utilities/circular_import_finder.py +222 -0
  3. abstract_utilities/circular_import_finder2.py +118 -0
  4. abstract_utilities/class_utils/__init__.py +7 -0
  5. abstract_utilities/class_utils/abstract_classes.py +144 -0
  6. abstract_utilities/class_utils/caller_utils.py +92 -0
  7. abstract_utilities/class_utils/class_utils.py +109 -0
  8. abstract_utilities/class_utils/function_utils.py +153 -0
  9. abstract_utilities/class_utils/global_utils.py +71 -0
  10. abstract_utilities/class_utils/imports/__init__.py +2 -0
  11. abstract_utilities/class_utils/imports/imports.py +2 -0
  12. abstract_utilities/class_utils/imports/utils.py +40 -0
  13. abstract_utilities/class_utils/module_utils.py +63 -0
  14. abstract_utilities/class_utils.py +0 -1
  15. abstract_utilities/directory_utils/__init__.py +2 -0
  16. abstract_utilities/directory_utils/directory_utils.py +94 -0
  17. abstract_utilities/directory_utils/imports/__init__.py +2 -0
  18. abstract_utilities/directory_utils/imports/imports.py +1 -0
  19. abstract_utilities/directory_utils/imports/module_imports.py +2 -0
  20. abstract_utilities/directory_utils/name_utils.py +43 -0
  21. abstract_utilities/directory_utils/size_utils.py +57 -0
  22. abstract_utilities/directory_utils/src/__init__.py +4 -0
  23. abstract_utilities/directory_utils/src/directory_utils.py +110 -0
  24. abstract_utilities/directory_utils/src/name_utils.py +43 -0
  25. abstract_utilities/directory_utils/src/size_utils.py +57 -0
  26. abstract_utilities/directory_utils/src/utils.py +116 -0
  27. abstract_utilities/directory_utils/utils.py +116 -0
  28. abstract_utilities/dynimport.py +1 -1
  29. abstract_utilities/env_utils/imports/imports.py +5 -3
  30. abstract_utilities/error_utils/__init__.py +2 -0
  31. abstract_utilities/error_utils/error_utils.py +25 -0
  32. abstract_utilities/error_utils/imports/__init__.py +2 -0
  33. abstract_utilities/error_utils/imports/imports.py +1 -0
  34. abstract_utilities/error_utils/imports/module_imports.py +1 -0
  35. abstract_utilities/file_utils/__init__.py +1 -2
  36. abstract_utilities/file_utils/file_utils/__init__.py +2 -0
  37. abstract_utilities/file_utils/file_utils/file_utils.py +3 -3
  38. abstract_utilities/file_utils/file_utils/find_collect.py +154 -0
  39. abstract_utilities/file_utils/file_utils/imports/__init__.py +3 -0
  40. abstract_utilities/file_utils/file_utils/imports/constants.py +39 -0
  41. abstract_utilities/file_utils/file_utils/imports/file_functions.py +10 -0
  42. abstract_utilities/file_utils/file_utils/imports/imports.py +39 -0
  43. abstract_utilities/file_utils/file_utils/imports/module_imports.py +14 -0
  44. abstract_utilities/file_utils/file_utils/imports.py +9 -0
  45. abstract_utilities/file_utils/file_utils/type_checks.py +92 -0
  46. abstract_utilities/file_utils/imports/__init__.py +1 -2
  47. abstract_utilities/file_utils/imports/classes.py +59 -55
  48. abstract_utilities/file_utils/imports/clean_imps.py +158 -0
  49. abstract_utilities/file_utils/imports/constants.py +84 -4
  50. abstract_utilities/file_utils/imports/file_functions.py +1 -1
  51. abstract_utilities/file_utils/imports/imports.py +40 -8
  52. abstract_utilities/file_utils/imports/module_imports.py +4 -5
  53. abstract_utilities/file_utils/module_imports.py +12 -0
  54. abstract_utilities/file_utils/src/__init__.py +7 -0
  55. abstract_utilities/file_utils/src/file_filters/__init__.py +1 -0
  56. abstract_utilities/file_utils/src/file_filters/ensure_utils.py +490 -0
  57. abstract_utilities/file_utils/src/file_filters/filter_params.py +150 -0
  58. abstract_utilities/file_utils/src/file_filters/filter_utils.py +78 -0
  59. abstract_utilities/file_utils/src/file_filters/predicate_utils.py +44 -0
  60. abstract_utilities/file_utils/src/file_filters.py +177 -0
  61. abstract_utilities/file_utils/src/file_reader.py +543 -0
  62. abstract_utilities/file_utils/src/file_utils.py +156 -0
  63. abstract_utilities/file_utils/src/filter_params.py +197 -0
  64. abstract_utilities/file_utils/src/find_collect.py +200 -0
  65. abstract_utilities/file_utils/src/find_content.py +210 -0
  66. abstract_utilities/file_utils/src/initFunctionsGen.py +293 -0
  67. abstract_utilities/file_utils/src/initFunctionsGens.py +280 -0
  68. abstract_utilities/file_utils/src/map_utils.py +29 -0
  69. abstract_utilities/file_utils/src/pdf_utils.py +300 -0
  70. abstract_utilities/file_utils/src/reader_utils/__init__.py +4 -0
  71. abstract_utilities/file_utils/src/reader_utils/directory_reader.py +53 -0
  72. abstract_utilities/file_utils/src/reader_utils/file_reader.py +543 -0
  73. abstract_utilities/file_utils/src/reader_utils/file_readers.py +376 -0
  74. abstract_utilities/file_utils/src/reader_utils/imports.py +18 -0
  75. abstract_utilities/file_utils/src/reader_utils/pdf_utils.py +300 -0
  76. abstract_utilities/file_utils/src/type_checks.py +91 -0
  77. abstract_utilities/file_utils (2)/__init__.py +2 -0
  78. abstract_utilities/file_utils (2)/imports/__init__.py +2 -0
  79. abstract_utilities/file_utils (2)/imports/constants.py +118 -0
  80. abstract_utilities/file_utils (2)/imports/imports/__init__.py +3 -0
  81. abstract_utilities/file_utils (2)/imports/imports/constants.py +119 -0
  82. abstract_utilities/file_utils (2)/imports/imports/imports.py +46 -0
  83. abstract_utilities/file_utils (2)/imports/imports/module_imports.py +8 -0
  84. abstract_utilities/file_utils (2)/imports/utils/__init__.py +3 -0
  85. abstract_utilities/file_utils (2)/imports/utils/classes.py +379 -0
  86. abstract_utilities/file_utils (2)/imports/utils/clean_imps.py +155 -0
  87. abstract_utilities/file_utils (2)/imports/utils/filter_utils.py +341 -0
  88. abstract_utilities/file_utils (2)/src/__init__.py +8 -0
  89. abstract_utilities/file_utils (2)/src/file_filters.py +155 -0
  90. abstract_utilities/file_utils (2)/src/file_reader.py +604 -0
  91. abstract_utilities/file_utils (2)/src/find_collect.py +258 -0
  92. abstract_utilities/file_utils (2)/src/initFunctionsGen.py +286 -0
  93. abstract_utilities/file_utils (2)/src/map_utils.py +28 -0
  94. abstract_utilities/file_utils (2)/src/pdf_utils.py +300 -0
  95. abstract_utilities/hash_utils/__init__.py +2 -0
  96. abstract_utilities/hash_utils/hash_utils.py +5 -0
  97. abstract_utilities/hash_utils/imports/__init__.py +2 -0
  98. abstract_utilities/hash_utils/imports/imports.py +1 -0
  99. abstract_utilities/hash_utils/imports/module_imports.py +0 -0
  100. abstract_utilities/history_utils/__init__.py +2 -0
  101. abstract_utilities/history_utils/history_utils.py +37 -0
  102. abstract_utilities/history_utils/imports/__init__.py +2 -0
  103. abstract_utilities/history_utils/imports/imports.py +1 -0
  104. abstract_utilities/history_utils/imports/module_imports.py +0 -0
  105. abstract_utilities/import_utils/__init__.py +2 -0
  106. abstract_utilities/import_utils/circular_import_finder.py +222 -0
  107. abstract_utilities/import_utils/circular_import_finder2.py +118 -0
  108. abstract_utilities/import_utils/imports/__init__.py +4 -0
  109. abstract_utilities/import_utils/imports/constants.py +2 -0
  110. abstract_utilities/import_utils/imports/imports.py +4 -0
  111. abstract_utilities/import_utils/imports/init_imports.py +3 -0
  112. abstract_utilities/import_utils/imports/module_imports.py +9 -0
  113. abstract_utilities/import_utils/imports/utils.py +30 -0
  114. abstract_utilities/import_utils/src/__init__.py +8 -0
  115. abstract_utilities/import_utils/src/clean_imports.py +278 -0
  116. abstract_utilities/import_utils/src/dot_utils.py +80 -0
  117. abstract_utilities/import_utils/src/extract_utils.py +46 -0
  118. abstract_utilities/import_utils/src/import_functions.py +110 -0
  119. abstract_utilities/import_utils/src/import_utils.py +349 -0
  120. abstract_utilities/import_utils/src/layze_import_utils/__init__.py +2 -0
  121. abstract_utilities/import_utils/src/layze_import_utils/lazy_utils.py +41 -0
  122. abstract_utilities/import_utils/src/layze_import_utils/nullProxy.py +37 -0
  123. abstract_utilities/import_utils/src/nullProxy.py +30 -0
  124. abstract_utilities/import_utils/src/package_utils/__init__.py +139 -0
  125. abstract_utilities/import_utils/src/package_utils/context_utils.py +27 -0
  126. abstract_utilities/import_utils/src/package_utils/import_collectors.py +53 -0
  127. abstract_utilities/import_utils/src/package_utils/path_utils.py +28 -0
  128. abstract_utilities/import_utils/src/package_utils/safe_import.py +27 -0
  129. abstract_utilities/import_utils/src/package_utils.py +140 -0
  130. abstract_utilities/import_utils/src/package_utilss/__init__.py +139 -0
  131. abstract_utilities/import_utils/src/package_utilss/context_utils.py +27 -0
  132. abstract_utilities/import_utils/src/package_utilss/import_collectors.py +53 -0
  133. abstract_utilities/import_utils/src/package_utilss/path_utils.py +28 -0
  134. abstract_utilities/import_utils/src/package_utilss/safe_import.py +27 -0
  135. abstract_utilities/import_utils/src/pkg_utils.py +194 -0
  136. abstract_utilities/import_utils/src/sysroot_utils.py +112 -0
  137. abstract_utilities/imports.py +21 -0
  138. abstract_utilities/json_utils/__init__.py +2 -0
  139. abstract_utilities/json_utils/imports/__init__.py +2 -0
  140. abstract_utilities/json_utils/imports/imports.py +2 -0
  141. abstract_utilities/json_utils/imports/module_imports.py +5 -0
  142. abstract_utilities/json_utils/json_utils.py +777 -0
  143. abstract_utilities/list_utils/__init__.py +2 -0
  144. abstract_utilities/list_utils/imports/__init__.py +2 -0
  145. abstract_utilities/list_utils/imports/imports.py +1 -0
  146. abstract_utilities/list_utils/imports/module_imports.py +0 -0
  147. abstract_utilities/list_utils/list_utils.py +202 -0
  148. abstract_utilities/log_utils/__init__.py +5 -0
  149. abstract_utilities/log_utils/abstractLogManager.py +64 -0
  150. abstract_utilities/log_utils/call_response.py +68 -0
  151. abstract_utilities/log_utils/imports/__init__.py +2 -0
  152. abstract_utilities/log_utils/imports/imports.py +7 -0
  153. abstract_utilities/log_utils/imports/module_imports.py +2 -0
  154. abstract_utilities/log_utils/log_file.py +162 -0
  155. abstract_utilities/log_utils/logger_callable.py +49 -0
  156. abstract_utilities/math_utils/__init__.py +2 -0
  157. abstract_utilities/math_utils/imports/__init__.py +2 -0
  158. abstract_utilities/math_utils/imports/imports.py +2 -0
  159. abstract_utilities/math_utils/imports/module_imports.py +1 -0
  160. abstract_utilities/math_utils/math_utils.py +208 -0
  161. abstract_utilities/parse_utils/__init__.py +2 -0
  162. abstract_utilities/parse_utils/imports/__init__.py +3 -0
  163. abstract_utilities/parse_utils/imports/constants.py +10 -0
  164. abstract_utilities/parse_utils/imports/imports.py +2 -0
  165. abstract_utilities/parse_utils/imports/module_imports.py +4 -0
  166. abstract_utilities/parse_utils/parse_utils.py +539 -0
  167. abstract_utilities/path_utils/__init__.py +2 -0
  168. abstract_utilities/path_utils/imports/__init__.py +3 -0
  169. abstract_utilities/path_utils/imports/imports.py +1 -0
  170. abstract_utilities/path_utils/imports/module_imports.py +8 -0
  171. abstract_utilities/path_utils/path_utils.py +248 -0
  172. abstract_utilities/path_utils.py +95 -14
  173. abstract_utilities/read_write_utils/__init__.py +1 -0
  174. abstract_utilities/read_write_utils/imports/__init__.py +2 -0
  175. abstract_utilities/read_write_utils/imports/imports.py +2 -0
  176. abstract_utilities/read_write_utils/imports/module_imports.py +5 -0
  177. abstract_utilities/read_write_utils/read_write_utils.py +439 -0
  178. abstract_utilities/read_write_utils.py +218 -10
  179. abstract_utilities/robust_reader/imports/imports.py +0 -9
  180. abstract_utilities/robust_readers/import_utils/__init__.py +1 -0
  181. abstract_utilities/robust_readers/import_utils/clean_imports.py +175 -0
  182. abstract_utilities/robust_readers/initFuncGen.py +10 -2
  183. abstract_utilities/safe_utils/__init__.py +2 -0
  184. abstract_utilities/safe_utils/imports/__init__.py +3 -0
  185. abstract_utilities/safe_utils/imports/imports.py +2 -0
  186. abstract_utilities/safe_utils/imports/module_imports.py +2 -0
  187. abstract_utilities/safe_utils/safe_utils.py +166 -0
  188. abstract_utilities/ssh_utils/__init__.py +3 -1
  189. abstract_utilities/ssh_utils/classes.py +0 -1
  190. abstract_utilities/ssh_utils/cmd_utils.py +207 -0
  191. abstract_utilities/ssh_utils/imports/__init__.py +3 -0
  192. abstract_utilities/ssh_utils/imports/imports.py +5 -0
  193. abstract_utilities/ssh_utils/imports/module_imports.py +6 -0
  194. abstract_utilities/ssh_utils/imports/utils.py +189 -0
  195. abstract_utilities/ssh_utils/imports.py +1 -2
  196. abstract_utilities/ssh_utils/pexpect_utils.py +11 -18
  197. abstract_utilities/ssh_utils/type_checks.py +92 -0
  198. abstract_utilities/string_clean.py +40 -1
  199. abstract_utilities/string_utils/__init__.py +4 -0
  200. abstract_utilities/string_utils/clean_utils.py +28 -0
  201. abstract_utilities/string_utils/eat_utils.py +103 -0
  202. abstract_utilities/string_utils/imports/__init__.py +3 -0
  203. abstract_utilities/string_utils/imports/imports.py +2 -0
  204. abstract_utilities/string_utils/imports/module_imports.py +2 -0
  205. abstract_utilities/string_utils/imports/utils.py +81 -0
  206. abstract_utilities/string_utils/replace_utils.py +27 -0
  207. abstract_utilities/string_utils.py +51 -0
  208. abstract_utilities/thread_utils/__init__.py +2 -0
  209. abstract_utilities/thread_utils/imports/__init__.py +2 -0
  210. abstract_utilities/thread_utils/imports/imports.py +2 -0
  211. abstract_utilities/thread_utils/imports/module_imports.py +2 -0
  212. abstract_utilities/thread_utils/thread_utils.py +140 -0
  213. abstract_utilities/time_utils/__init__.py +2 -0
  214. abstract_utilities/time_utils/imports/__init__.py +2 -0
  215. abstract_utilities/time_utils/imports/imports.py +3 -0
  216. abstract_utilities/time_utils/imports/module_imports.py +1 -0
  217. abstract_utilities/time_utils/time_utils.py +392 -0
  218. abstract_utilities/type_utils/__init__.py +7 -0
  219. abstract_utilities/type_utils/alpha_utils.py +59 -0
  220. abstract_utilities/type_utils/get_type.py +120 -0
  221. abstract_utilities/type_utils/imports/__init__.py +3 -0
  222. abstract_utilities/type_utils/imports/constants.py +134 -0
  223. abstract_utilities/type_utils/imports/imports.py +4 -0
  224. abstract_utilities/type_utils/imports/module_imports.py +25 -0
  225. abstract_utilities/type_utils/is_type.py +455 -0
  226. abstract_utilities/type_utils/make_type.py +126 -0
  227. abstract_utilities/type_utils/mime_types.py +68 -0
  228. abstract_utilities/type_utils/num_utils.py +19 -0
  229. abstract_utilities/type_utils/type_utils.py +104 -0
  230. abstract_utilities/type_utils.py +25 -1
  231. {abstract_utilities-0.2.2.442.dist-info → abstract_utilities-0.2.2.688.dist-info}/METADATA +1 -1
  232. abstract_utilities-0.2.2.688.dist-info/RECORD +288 -0
  233. imports/__init__.py +36 -0
  234. abstract_utilities-0.2.2.442.dist-info/RECORD +0 -82
  235. {abstract_utilities-0.2.2.442.dist-info → abstract_utilities-0.2.2.688.dist-info}/WHEEL +0 -0
  236. {abstract_utilities-0.2.2.442.dist-info → abstract_utilities-0.2.2.688.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,439 @@
1
+ """
2
+ read_write_utils.py
3
+ -------------------
4
+ Unified read/write utility for safe file operations.
5
+ Supports:
6
+ - Writing content to a file
7
+ - Reading content from a file
8
+ - Creating and reading if missing
9
+ - Detecting file/content params via positional args or kwargs
10
+
11
+ Usage:
12
+ from abstract_utilities.read_write_utils import *
13
+ """
14
+
15
+ from .imports import *
16
+ _FILE_PATH_KEYS = ['file', 'filepath', 'file_path', 'path', 'directory', 'f', 'dst', 'dest']
17
+ _CONTENTS_KEYS = ['cont', 'content', 'contents', 'data', 'datas', 'dat', 'src', 'source']
18
+ from pathlib import Path
19
+ import uuid
20
+ import shlex
21
+
22
+ _STAGE_ROOT = Path("/var/tmp/abstract_stage")
23
+
24
+
25
+ def _stage_file(contents: str, suffix=".tmp") -> Path:
26
+ """
27
+ Write contents to a local staging file.
28
+ """
29
+ _STAGE_ROOT.mkdir(parents=True, exist_ok=True)
30
+ path = _STAGE_ROOT / f"{uuid.uuid4().hex}{suffix}"
31
+ path.write_text(str(contents), encoding="utf-8")
32
+ return path
33
+
34
+
35
+ def _install_file(staged: Path, dest: str, **kwargs) -> str:
36
+ """
37
+ Atomically install a staged file to destination using sudo install.
38
+ """
39
+ cmd = (
40
+ f"sudo install -D -m 0644 "
41
+ f"{shlex.quote(str(staged))} "
42
+ f"{shlex.quote(dest)}"
43
+ )
44
+ return run_local_cmd(
45
+ cmd=cmd,
46
+ password=kwargs.get("password"),
47
+ key=kwargs.get("key"),
48
+ env_path=kwargs.get("env_path"),
49
+ )
50
+
51
+
52
+ # --- Helper utilities --------------------------------------------------------
53
+ def string_in_keys(strings, kwargs):
54
+ """Find a matching keyword in kwargs that contains any of the given substrings."""
55
+ for key in kwargs:
56
+ for s in strings:
57
+ if s.lower() in key.lower():
58
+ return key
59
+ return None
60
+ def make_dirs(path, exist_ok=True, **kwargs):
61
+ remote = get_user_pass_host_key(**kwargs)
62
+
63
+ if remote:
64
+ kwargs['cmd'] = f"mkdir -p {path}"
65
+
66
+ resp = run_pruned_func(run_cmd, **kwargs)
67
+
68
+ else:
69
+ os.makedirs(path, exist_ok=exist_ok)
70
+ return path
71
+ def make_path(path, home_dir=None, file=None, **kwargs):
72
+ if not path:
73
+ return None
74
+
75
+ basename = os.path.basename(path)
76
+ parts = [p for p in path.split('/') if p]
77
+
78
+ # Detect whether this is a file or a folder
79
+ is_file = file if file is not None else ('.' in basename)
80
+ pieces = parts[:-1] if is_file else parts
81
+
82
+ full_dir = home_dir or '/'
83
+ for piece in pieces:
84
+ full_dir = os.path.join(full_dir, piece)
85
+ make_dirs(full_dir, exist_ok=True, **kwargs)
86
+
87
+ if is_file:
88
+ full_dir = os.path.join(full_dir, basename)
89
+
90
+ return full_dir
91
+ def get_rel_path(src,src_rel,dst,**kwargs):
92
+ if src.startswith(src_rel):
93
+ nu_src = src[len(src_rel):]
94
+ nu_src= eatAll(nu_src,'/')
95
+ directory= eatOuter(dst,'/')
96
+ rel_path = os.path.join(dst,nu_src)
97
+ return rel_path
98
+ def make_relative_path(src,src_rel,dst,**kwargs):
99
+
100
+ if src.startswith(src_rel):
101
+ rel_path = get_rel_path(src,src_rel,dst)
102
+
103
+ path = make_path(rel_path,**kwargs)
104
+
105
+ return path
106
+
107
+ def path_join(*args):
108
+ path = None
109
+ for i,arg in enumerate(args):
110
+ if arg:
111
+ if i == 0:
112
+ path = arg
113
+ else:
114
+ path = os.path.join(path,arg)
115
+ return path
116
+
117
+ def get_path(paths,**kwargs):
118
+ """Return the first valid path among given paths."""
119
+ for path in paths:
120
+ if isinstance(path, str):
121
+ if is_file(path,**kwargs):
122
+ return path
123
+ dirname = os.path.dirname(path)
124
+ if is_exists(dirname,**kwargs):
125
+ return path
126
+ return None
127
+
128
+
129
+ def break_down_find_existing(path,**kwargs):
130
+ """Return the first non-existent subpath within a path chain."""
131
+ test_path = ''
132
+ for part in path.split(os.sep):
133
+ test_path = os.path.join(test_path, part)
134
+ if not is_exists(test_path,**kwargs):
135
+ return test_path if test_path else None
136
+ return test_path
137
+
138
+
139
+ # --- Parameter parsing --------------------------------------------------------
140
+ def check_read_write_params(*args, **kwargs):
141
+ """
142
+ Determine file_path and contents from arguments.
143
+ Returns a tuple: (file_path, contents)
144
+ """
145
+ file_key = string_in_keys(_FILE_PATH_KEYS, kwargs)
146
+ content_key = string_in_keys(_CONTENTS_KEYS, kwargs)
147
+
148
+ file_path = kwargs.get(file_key) if file_key else None
149
+ contents = kwargs.get(content_key) if content_key else None
150
+
151
+ # Handle positional args (fallback)
152
+ if file_path is None and len(args) > 0:
153
+ file_path = args[0]
154
+ if contents is None and len(args) > 1:
155
+ contents = args[1]
156
+
157
+ if file_path is None:
158
+ raise ValueError("Missing file_path argument.")
159
+ return file_path, contents
160
+
161
+ def write_to_path(
162
+ file_path: str,
163
+ contents: str,
164
+ *,
165
+ user_at_host: str = None,
166
+ cwd: str | None = None,
167
+ password=None,
168
+ key=None,
169
+ env_path=None,
170
+ **kwargs
171
+ ) -> str:
172
+ """
173
+ Completely overwrite a file (locally or remotely).
174
+ Supports sudo and password-based remote execution.
175
+ """
176
+
177
+ # sanitize for shell safety
178
+ quoted_path = shlex.quote(file_path)
179
+ quoted_data = shlex.quote(str(contents))
180
+
181
+ # shell command that fully overwrites
182
+ # (no append, replaces contents entirely)
183
+ base_cmd = f'sudo sh -c "echo {quoted_data} > {quoted_path}"'
184
+
185
+ # optional sudo password injection
186
+ full_cmd = get_print_sudo_cmd(
187
+ cmd=base_cmd,
188
+ password=password,
189
+ key=key,
190
+ env_path=env_path
191
+ )
192
+
193
+ # local or remote dispatch
194
+ if user_at_host:
195
+ return run_remote_cmd(
196
+ user_at_host=user_at_host,
197
+ cmd=full_cmd,
198
+ cwd=cwd,
199
+ password=password,
200
+ key=key,
201
+ env_path=env_path,
202
+ **kwargs
203
+ )
204
+ else:
205
+ return run_local_cmd(
206
+ cmd=full_cmd,
207
+ cwd=cwd,
208
+ password=password,
209
+ key=key,
210
+ env_path=env_path,
211
+ **kwargs
212
+ )
213
+ ### --- Core functionality -------------------------------------------------------
214
+ ##def write_to_file(*args, **kwargs):
215
+ ## """
216
+ ## Write contents to a file (create if missing).
217
+ ##
218
+ ## Returns the file_path written.
219
+ ## """
220
+ ## file_path, contents = check_read_write_params(*args, **kwargs)
221
+ ## if contents is None:
222
+ ## raise ValueError("Missing contents to write.")
223
+ ##
224
+ ## os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True)
225
+ ## with open(file_path, "w", encoding="utf-8") as f:
226
+ ## f.write(str(contents))
227
+ ## return file_path
228
+ # --- Core functionality -------------------------------------------------------
229
+ ##def write_to_file(*args, **kwargs):
230
+ ## """
231
+ ## Write contents to a file (create if missing).
232
+ ##
233
+ ## Returns the file_path written.
234
+ ## """
235
+ ## file_path, contents = check_read_write_params(*args, **kwargs)
236
+ ## values,kwargs = get_from_kwargs(['file_path','contents'],del_kwarg=True,**kwargs)
237
+ ## dirname = os.path.dirname(file_path)
238
+ ##
239
+ ## if contents is None:
240
+ ## raise ValueError("Missing contents to write.")
241
+ ## user_at_host = kwargs.get("user_at_host")
242
+ ## if get_user_pass_host_key(**kwargs):
243
+ ## make_dirs(dirname, exist_ok=True,**kwargs)
244
+ ## kwargs["cwd"] = kwargs.get('cwd') or os.path.dirname(file_path)
245
+ ## # sanitize for shell safety
246
+ ## quoted_path = shlex.quote(file_path)
247
+ ## quoted_data = shlex.quote(str(contents))
248
+ ## # shell command that fully overwrites
249
+ ## # (no append, replaces contents entirely)
250
+ ## kwargs["cmd"] = f'sh -c "echo {quoted_data} > {quoted_path}"'
251
+ ## if not kwargs.get('password') and not kwargs.get('key'):
252
+ ## kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
253
+ ## result = run_pruned_func(run_cmd,**kwargs)
254
+ ## if 'file_path' in kwargs:
255
+ ## del kwargs['file_path']
256
+ ## if not is_file(file_path,**kwargs) or str(contents) != read_from_file(file_path,**kwargs):
257
+ ## kwargs["cmd"]=f'sudo {kwargs["cmd"]}'
258
+ ## result = run_pruned_func(run_cmd,**kwargs)
259
+ ## return result
260
+ ##
261
+ ## make_dirs(dirname or ".", exist_ok=True)
262
+ ## with open(file_path, "w", encoding="utf-8") as f:
263
+ ## f.write(str(contents))
264
+ ## return file_path
265
+
266
+ def _should_use_remote(**kwargs) -> bool:
267
+ """
268
+ Only use remote mode IF:
269
+ - user_at_host is provided
270
+ - AND password/key is provided
271
+ Otherwise: local write.
272
+ """
273
+ user = kwargs.get("user_at_host")
274
+ if not user:
275
+ return False # not remote
276
+
277
+ # If user_at_host is provided, then password or key MUST be present
278
+ if kwargs.get("password") or kwargs.get("key"):
279
+ return True
280
+
281
+ return False # user provided but no auth → treat as local
282
+
283
+
284
+ def _write_to_file(contents: str, file_path: str, **kwargs) -> str:
285
+ """
286
+ Unified writer using stage → install model.
287
+ """
288
+
289
+ remote = _should_use_remote(**kwargs)
290
+
291
+ # --- Remote path (unchanged conceptually) ---
292
+ if remote:
293
+ tmp_path = _stage_file(contents)
294
+
295
+ user_at_host = kwargs["user_at_host"]
296
+ password = kwargs.get("password")
297
+ key = kwargs.get("key")
298
+
299
+ # copy staged file
300
+ scp_cmd = (
301
+ f"scp {shlex.quote(str(tmp_path))} "
302
+ f"{shlex.quote(user_at_host)}:{shlex.quote(file_path)}"
303
+ )
304
+ return run_pruned_func(
305
+ run_local_cmd,
306
+ cmd=scp_cmd,
307
+ password=password,
308
+ key=key,
309
+ **kwargs
310
+ )
311
+
312
+ # --- Local path ---
313
+ try:
314
+ # Attempt direct write for non-privileged paths
315
+ os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True)
316
+ with open(file_path, "w", encoding="utf-8") as f:
317
+ f.write(str(contents))
318
+ return file_path
319
+
320
+ except (PermissionError, FileNotFoundError):
321
+ # Privileged path → stage + install
322
+ staged = _stage_file(contents)
323
+ return _install_file(staged, file_path, **kwargs)
324
+
325
+
326
+
327
+ def write_to_file(*, contents: str, file_path: str, **kwargs):
328
+ """
329
+ Error-handled public writer.
330
+ """
331
+ try:
332
+ return _write_to_file(contents=contents, file_path=file_path, **kwargs)
333
+ except Exception as e:
334
+ print("WRITE ERROR:", e)
335
+ raise RuntimeError(f"Failed writing: {file_path}")
336
+ def read_from_file(file_path=None,**kwargs):
337
+ if get_user_pass_host_key(**kwargs):
338
+ kwargs["cwd"] = kwargs.get('cwd') or os.path.dirname(file_path)
339
+ basename = os.path.basename(file_path)
340
+ kwargs["cmd"] = f'cat {basename}'
341
+ return run_pruned_func(run_cmd,**kwargs)
342
+ """Read text content from a file."""
343
+ with open(file_path, "r", encoding="utf-8") as f:
344
+ return f.read()
345
+
346
+
347
+ def copy_dirs(dirs, dst_root, src_rel=None, **kwargs):
348
+ """
349
+ Recursively copy directory structures (without files) from dirs → dst_root.
350
+ """
351
+ for src in dirs:
352
+ # build destination path preserving relative structure
353
+ dst_path = make_relative_path(src, src_rel, dst_root, **kwargs) if src_rel else dst_root
354
+ make_path(dst_path, **kwargs) # ensures directory exists
355
+
356
+
357
+
358
+ def copy_file(src, dst_root, src_rel=None, **kwargs):
359
+ """
360
+ Copy a single file to dst_root, preserving relative structure if src_rel provided.
361
+ Supports remote copy via read/write.
362
+ """
363
+ # derive destination file path
364
+ dst_path = make_relative_path(src, src_rel, dst_root, **kwargs) if src_rel else os.path.join(dst_root, os.path.basename(src))
365
+ make_path(dst_path, **kwargs)
366
+
367
+ if get_user_pass_host_key(**kwargs): # remote mode
368
+ contents = read_from_file(src, **kwargs)
369
+ write_to_file(contents=contents, file_path=dst_path, **kwargs)
370
+ else: # local
371
+ os.makedirs(os.path.dirname(dst_path), exist_ok=True)
372
+ shutil.copy2(src, dst_path)
373
+
374
+
375
+ return dst_path
376
+
377
+
378
+ def copy_files(files, dst_root, src_rel=None, **kwargs):
379
+ """
380
+ Copy a list of files to dst_root.
381
+ """
382
+ for src in files:
383
+ copy_file(src=src, dst_root=dst_root, src_rel=src_rel, **kwargs)
384
+
385
+ def create_and_read_file(*args, **kwargs):
386
+ """
387
+ Create the file (if missing) and read contents from it.
388
+ """
389
+ file_path, contents = check_read_write_params(*args, **kwargs)
390
+ if not os.path.isfile(file_path):
391
+ write_to_file(file_path, contents or "")
392
+ return read_from_file(file_path)
393
+
394
+
395
+ def is_file_extension(obj: str) -> bool:
396
+ """Return True if obj looks like a filename with extension."""
397
+ if not isinstance(obj, str):
398
+ return False
399
+ root, ext = os.path.splitext(obj)
400
+ return bool(root and ext)
401
+
402
+
403
+ def delete_file(file_path: str):
404
+ """Safely delete a file if it exists."""
405
+ if os.path.isfile(file_path):
406
+ os.remove(file_path)
407
+ return True
408
+ return False
409
+
410
+
411
+ def get_content_lines(*args, **kwargs):
412
+ """Return a list of lines from string or file path."""
413
+ file_path, contents = check_read_write_params(*args, **kwargs)
414
+ if os.path.isfile(file_path):
415
+ contents = read_from_file(filepath)
416
+
417
+ if isinstance(contents, str):
418
+ return contents.splitlines()
419
+ elif isinstance(contents, list):
420
+ return contents
421
+ return []
422
+ def collate_text_docs(directory=None):
423
+ return [read_from_file(item) for item in get_all_files(directory=directory)]
424
+ def get_content(*paths):
425
+ item_path = os.path.join(*paths)
426
+ if os.path.isfile(item_path):
427
+ try:
428
+ content = read_from_file(item_path)
429
+ return content
430
+ except:
431
+ pass
432
+ return None
433
+ def get_text_or_read(text=None,file_path=None):
434
+ text = text or ''
435
+ imports_js = {}
436
+ if not text and file_path and os.path.isfile(file_path):
437
+ text=read_from_file(file_path)
438
+ return text
439
+ ##