pyosal 0.1.0__tar.gz

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.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) since 2026: pyosal Python Module
4
+ description: python Operating System Abstract Layer
5
+ author: J. Arrizza email: cppgent0@gmail.com
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ graft pyosal
2
+ global-exclude *.py[cod]
pyosal-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyosal
3
+ Version: 0.1.0
4
+ Summary: python Operating System Abstract Layer
5
+ Author-email: "J. Arrizza" <cppgent0@gmail.com>
6
+ Maintainer-email: "J. Arrizza" <cppgent0@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Website, https://arrizza.com/pyosal
9
+ Project-URL: Source, https://bitbucket.org/arrizza-public/pyosal/src/master
10
+ Project-URL: Download, https://bitbucket.org/arrizza-public/pyosal/get/master.zip
11
+ Keywords: SHOULD-NOT-MATCH,SHOULD-NOT-MATCH,SHOULD-NOT-MATCH
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Topic :: Utilities
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.txt
21
+ Dynamic: license-file
22
+
23
+ * website: [Website](https://arrizza.com/TODO.html)
24
+ * installation: [Common Setup](https://arrizza.com/setup-common.html)
25
+
26
+ ## Summary
27
+
28
+ ***Under Construction***
pyosal-0.1.0/README.md ADDED
@@ -0,0 +1,6 @@
1
+ * website: [Website](https://arrizza.com/TODO.html)
2
+ * installation: [Common Setup](https://arrizza.com/setup-common.html)
3
+
4
+ ## Summary
5
+
6
+ ***Under Construction***
@@ -0,0 +1,53 @@
1
+ [project]
2
+ description = 'python Operating System Abstract Layer'
3
+ keywords = ['SHOULD-NOT-MATCH', 'SHOULD-NOT-MATCH', 'SHOULD-NOT-MATCH']
4
+
5
+ classifiers = [
6
+ # "Private :: Do Not Upload", # use to prevent upload
7
+ # Choose either '3 - Alpha', '4 - Beta' or '5 - Production/Stable'
8
+ 'Development Status :: 3 - Alpha',
9
+ 'Environment :: Console',
10
+ 'Topic :: Utilities',
11
+ 'Intended Audience :: Developers',
12
+ 'Programming Language :: Python :: 3.10',
13
+ 'Programming Language :: Python :: 3.12',
14
+ ]
15
+ # additional packages to install
16
+ dependencies = [
17
+ # 'falcon-logger',
18
+ ]
19
+
20
+ # common values
21
+ name = 'pyosal'
22
+ version = '0.1.0'
23
+ requires-python = '>=3.9'
24
+ authors = [{ name = 'J. Arrizza', email = 'cppgent0@gmail.com' }]
25
+ maintainers = [
26
+ { name = 'J. Arrizza', email = 'cppgent0@gmail.com' },
27
+ ]
28
+ readme = { file = 'README.md', content-type = 'text/markdown' }
29
+ license = 'MIT'
30
+
31
+ [project.optional-dependencies]
32
+ # None
33
+ [project.scripts]
34
+ # None e.g. spam-cli = "spam:main_cli"
35
+
36
+ [project.urls]
37
+ 'Website' = 'https://arrizza.com/pyosal'
38
+ 'Source' = 'https://bitbucket.org/arrizza-public/pyosal/src/master'
39
+ 'Download' = 'https://bitbucket.org/arrizza-public/pyosal/get/master.zip'
40
+ # 'Bug Reports' = 'TODO https://github.com/pypa/sampleproject/issues'
41
+ # 'Funding' = 'TODO https://donate.pypi.org'
42
+ # 'Say Thanks!' = 'TODO http://saythanks.io/to/example'
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ['src']
46
+ exclude = ['.gitignore']
47
+
48
+ [tool.setuptools]
49
+ include-package-data = true
50
+
51
+ [build-system]
52
+ requires = ['setuptools >=61.0']
53
+ build-backend = 'setuptools.build_meta'
pyosal-0.1.0/setup.cfg ADDED
@@ -0,0 +1,43 @@
1
+ [metadata]
2
+ description_file = README.md
3
+
4
+ [pylint.MESSAGES CONTROL]
5
+ max-line-length = 140
6
+ disable = C0114,C0115,C0116,C0415,R0801,R0903,R0912,R0913,R0917,W0511
7
+
8
+ [pycodestyle]
9
+ max-line-length = 140
10
+ ignore = E266,E402,E731,E203
11
+ statistics = True
12
+ exclude = venv/*, out/*, pf_*, setup.py, debug/*, release/*
13
+
14
+ [coverage:run]
15
+ omit =
16
+ ./conftest.py
17
+ ./out/*
18
+ ./ut/*
19
+ ./venv/*
20
+ ./ver/*
21
+ build_info.py
22
+ branch = true
23
+
24
+ [coverage:report]
25
+ exclude_lines =
26
+ pragma: no cover
27
+
28
+ def __repr__
29
+ if self\.debug
30
+
31
+ raise NotImplementedError
32
+
33
+ if 0:
34
+ if __main__ == .__main__:
35
+ ignore_errors = True
36
+
37
+ [coverage:html]
38
+ directory = ./out/coverage
39
+
40
+ [egg_info]
41
+ tag_build =
42
+ tag_date = 0
43
+
@@ -0,0 +1,3 @@
1
+ from .pyosal import PyOsal
2
+
3
+ pyosal = PyOsal()
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from .constants_version import ConstantsVersion
4
+
5
+
6
+ # -------------------
7
+ ## Holds various constants
8
+ @dataclass
9
+ class Constants(ConstantsVersion): # pylint: disable=too-few-public-methods
10
+ pass
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ # --------------------
5
+ ## holds constants
6
+ @dataclass
7
+ class ConstantsVersion:
8
+ ## current App version
9
+ version = '0.1.0'
@@ -0,0 +1,565 @@
1
+ import getpass
2
+ import os
3
+ import platform
4
+ import re
5
+ import subprocess
6
+ import sys
7
+ import threading
8
+
9
+
10
+ # -------------------
11
+ ## Meta class for pyosal; ensures it is a singleton
12
+ class _PyOsalMeta(type):
13
+ ## holds instances of this class (should be 1)
14
+ _instances = {}
15
+ ## ensure creation is thread safe
16
+ _lock = threading.Lock()
17
+
18
+ # -------------------
19
+ ## handle getting the instance of the class.
20
+ # @param args the arguments
21
+ # @param kwargs the kw arguments
22
+ def __call__(cls, *args, **kwargs):
23
+ # Double-checked locking for performance and thread safety
24
+ if cls not in cls._instances: # pragma: no cover
25
+ with cls._lock:
26
+ if cls not in cls._instances: # pragma: no cover
27
+ # super().__call__ handles object creation AND initialization
28
+ instance = super().__call__(*args, **kwargs)
29
+ cls._instances[cls] = instance
30
+ return cls._instances[cls]
31
+
32
+
33
+ # -------------------
34
+ ## holds OS specific info; there are four recognized OS:
35
+ # OS os_name
36
+ # ------- ----------
37
+ # Ubuntu : ubuntu
38
+ # Mac : macos
39
+ # Windows : win (msys2)
40
+ # Windows : psi (powershell)
41
+ # RPI : rpi
42
+ class PyOsal(metaclass=_PyOsalMeta):
43
+ # -------------------
44
+ ## initialize
45
+ def __init__(self):
46
+ ## holds the OS name
47
+ self._os_name: str = None
48
+ ## holds the OS version info
49
+ self._os_version: str
50
+ ## holds the python version
51
+ self._python_version: str = None
52
+ ## root directory of projects directory
53
+ # note returns windows format C:/xx/xx
54
+ self._root_dir: str = None
55
+ ## root directory of projects directory in msys2 format /c/xx/xx
56
+ self._root_dir_msys2: str = None
57
+ ## current userid
58
+ self._userid: str = None
59
+ ## hostname of the pc
60
+ self._hostname: str = None
61
+ ## list of valid OS
62
+ self._os_valid = ['ubuntu', 'macos', 'msys2', 'psi', 'rpi']
63
+
64
+ # === UT related
65
+
66
+ ## used by UTs only
67
+ self._ut_mode = False
68
+ ## used by UTs only
69
+ self.ut_curr_dir = None
70
+
71
+ # === initialize
72
+ self._get_os_name()
73
+ self._set_all_data()
74
+
75
+ # -------------------
76
+ ## get OS name/tag
77
+ # @return os_name
78
+ @property
79
+ def os_name(self):
80
+ return self._os_name
81
+
82
+ # -------------------
83
+ ## get OS version info
84
+ # @return os_version
85
+ @property
86
+ def os_version(self):
87
+ return self._os_version
88
+
89
+ # -------------------
90
+ ## get python version
91
+ # @return python_version
92
+ @property
93
+ def python_version(self):
94
+ return self._python_version
95
+
96
+ # -------------------
97
+ ## get path for root directory
98
+ # @return root_dir
99
+ @property
100
+ def root_dir(self):
101
+ return self._root_dir
102
+
103
+ # -------------------
104
+ ## get root directory for msys2 shells
105
+ # @return root_dir_msys2
106
+ @property
107
+ def root_dir_msys2(self):
108
+ return self._root_dir_msys2
109
+
110
+ # -------------------
111
+ ## get current userid
112
+ # @return userid
113
+ @property
114
+ def userid(self):
115
+ return self._userid
116
+
117
+ # -------------------
118
+ ## get current PC's hostname
119
+ # @return hostname
120
+ @property
121
+ def hostname(self):
122
+ return self._hostname
123
+
124
+ # -------------------
125
+ ## get list of recognized valid OS names
126
+ # @return os_valid
127
+ @property
128
+ def os_valid(self):
129
+ return self._os_valid
130
+
131
+ # -------------------
132
+ ## selects the current platform and sets impl to it
133
+ #
134
+ # @return None
135
+ def _set_all_data(self):
136
+ if self._os_name == 'rpi':
137
+ # TODO confirm this is correct
138
+ self._os_version = f'RPI {platform.system()} {platform.release()}'
139
+ self._root_dir = '~'
140
+ self._root_dir_msys2 = self._root_dir
141
+ elif self._os_name == 'msys2':
142
+ self._os_version = f'win32 {platform.system()} {platform.release()}'
143
+ self._root_dir = os.path.expanduser('~')
144
+ self._root_dir = self._root_dir.replace('\\', '/')
145
+ self._root_dir_msys2 = '/' + self._root_dir[0].lower() + self._root_dir[2:]
146
+ elif self._os_name == 'macos':
147
+ if self._ut_mode: # pragma: no cover
148
+ self._os_version = 'macOS 14.5'
149
+ else:
150
+ self._os_version = f'macOS {platform.mac_ver()[0]}' # pragma: no cover
151
+ self._root_dir = '~'
152
+ self._root_dir_msys2 = self._root_dir
153
+ elif self._os_name == 'ubuntu':
154
+ if self._ut_mode:
155
+ self._os_version = 'Ubuntu 24.04 noble'
156
+ else:
157
+ self._os_version = self._ubuntu_os_version()
158
+ self._root_dir = '~'
159
+ self._root_dir_msys2 = self._root_dir
160
+ else:
161
+ self.abort(f'PyOsal: unrecognized OS: "{sys.platform}"')
162
+
163
+ ## holds python version e.g. "Python 3.10"
164
+ self._python_version = f'Python {sys.version_info.major}.{sys.version_info.minor}'
165
+
166
+ self._hostname = platform.uname().node
167
+ self._hostname = self._hostname.lower().replace('.local', '')
168
+
169
+ self._userid = getpass.getuser()
170
+
171
+ # -------------------
172
+ ## get the current OS tag
173
+ # @return None
174
+ def _get_os_name(self): # pragma: no cover
175
+ if os.path.isfile('/sys/firmware/devicetree/base/model'):
176
+ self._os_name = 'rpi'
177
+ elif sys.platform == 'win32':
178
+ # TODO check if it's a cygwin?
179
+ if 'MSYSTEM' in os.environ:
180
+ self._os_name = 'msys2'
181
+ elif 'PSModulePath' in os.environ or 'POWERSHELL_DISTRIBUTION_CHANNEL' in os.environ:
182
+ self._os_name = 'psi'
183
+ else:
184
+ self._os_name = 'win-unknown'
185
+ elif sys.platform == 'darwin':
186
+ self._os_name = 'macos'
187
+ elif sys.platform == 'linux':
188
+ self._os_name = 'ubuntu'
189
+ else:
190
+ self._os_name = 'unknown'
191
+
192
+ # -------------------
193
+ ## holds OS information for Ubuntu
194
+ #
195
+ # @return string indicating OS info
196
+ def _ubuntu_os_version(self):
197
+ proc = subprocess.Popen( # pylint: disable=consider-using-with
198
+ ['lsb_release', '-a'],
199
+ stdout=subprocess.PIPE,
200
+ stderr=subprocess.STDOUT,
201
+ )
202
+ (out, _) = proc.communicate()
203
+ out = out.decode('utf-8')
204
+
205
+ version = 'notset'
206
+ codename = 'notset'
207
+ for line in out.split('\n'):
208
+ # sys.stdout.write(f'line: "{line}"')
209
+ args = line.split('\t')
210
+ if args[0] == 'Release:':
211
+ version = args[1]
212
+ elif args[0] == 'Codename:':
213
+ codename = args[1]
214
+ return f'Ubuntu {version} {codename}'
215
+
216
+ # === cmd invocation from here on
217
+
218
+ ## exception keywords
219
+ _excp_keywords = [
220
+ 'Traceback (most recent call last):',
221
+ 'Error:',
222
+ 'Permission denied',
223
+ 'command not found',
224
+ ]
225
+
226
+ # -------------------
227
+ ## run a command in a subprocess
228
+ #
229
+ # @param cmd the command to run
230
+ # @param working_dir the working directory to run the command in; default is '.'
231
+ # @param use_raw_log no prefix on line
232
+ # @param log_file the log_file to write the lines to
233
+ # @param print_cb callback function used to print output; default is None
234
+ # @param env the environment variables to use
235
+ # @param quiet if True, do not write to stdout
236
+ # @return rc - return code, lines - the output as a list of lines
237
+ def run_cmd( # pylint: disable=too-many-branches,too-many-statements
238
+ self,
239
+ cmd,
240
+ working_dir='.',
241
+ use_raw_log=False,
242
+ log_file=None,
243
+ print_cb=None,
244
+ env=None,
245
+ quiet=False,
246
+ ): # pylint: disable=too-many-locals
247
+ if not isinstance(cmd, list):
248
+ self.abort('PyOsal: cmd must be a list')
249
+
250
+ ## os_name: see above
251
+ if self._os_name == 'msys2':
252
+ # need to invokve msys2 bash
253
+ pfx = ['c:/msys64/usr/bin/bash', '-c']
254
+ # the current cmd is a single string at the end
255
+ pfx.append(' '.join(cmd))
256
+ cmd = pfx
257
+
258
+ # msys2 can cause truncations and wrapping of text if the lines are long
259
+ if env is None:
260
+ env = os.environ.copy()
261
+
262
+ fp = None
263
+ if log_file is not None:
264
+ fp = open(log_file, 'w', encoding='utf-8', newline='\n') # pylint: disable=consider-using-with
265
+
266
+ # TODO set to 127 and then set specifically to rc == 0 below
267
+ rc = 0
268
+ lineno = 0
269
+ lines = []
270
+
271
+ # ensure all output is printed, so any stdin done by the cmd is visible in the right order
272
+ self.stdout_flush()
273
+ try:
274
+ proc = subprocess.Popen( # pylint: disable=consider-using-with
275
+ cmd,
276
+ cwd=working_dir,
277
+ bufsize=0,
278
+ universal_newlines=True,
279
+ errors='replace',
280
+ text=True,
281
+ stdin=subprocess.DEVNULL,
282
+ stdout=subprocess.PIPE,
283
+ stderr=subprocess.STDOUT,
284
+ env=env,
285
+ )
286
+ except Exception as excp: # pylint: disable=broad-exception-caught
287
+ err_type = excp.__class__.__name__
288
+ line = f'EXCP pyosal.run_cmd: {err_type}: {excp}'
289
+ self._save_line(line, lineno, lines, fp, print_cb, use_raw_log, quiet)
290
+ return rc, lines
291
+
292
+ buffer = ''
293
+ while True:
294
+ chunk = os.read(proc.stdout.fileno(), 256)
295
+ if not chunk:
296
+ break
297
+ chunk_str = chunk.decode('utf-8', errors='surrogateescape')
298
+ chunk_str = chunk_str.replace('\r', '')
299
+
300
+ # for char in chunk:
301
+ # sys.stdout.write(f"'{char!r}': 0x{ord(char):02x}")
302
+
303
+ buffer += chunk_str
304
+ if '\n' not in buffer:
305
+ continue
306
+
307
+ buff_lines = buffer.split('\n')
308
+ buffer = buff_lines.pop()
309
+
310
+ for line in buff_lines:
311
+ # if not line.isprintable():
312
+ # sys.stdout.write('has hex: ', ascii(line), '\n')
313
+
314
+ # check for exceptions and other script failures
315
+ if any(keyword in line for keyword in self._excp_keywords):
316
+ excp_msg = '@@>>@@ Exception detected'
317
+ if excp_msg in line:
318
+ rc += 1
319
+ continue
320
+ sys.stdout.write(f' -- {excp_msg}: "{line}"\n')
321
+ rc += 1
322
+
323
+ if 'DEPRECATE' in line:
324
+ dep_msg = '@@>>@@ Deprecation detected'
325
+ if dep_msg in line:
326
+ rc += 1
327
+ continue
328
+ rc += 1
329
+ sys.stdout.write(f' -- {dep_msg} {rc}: "{line}"\n')
330
+
331
+ if self._skip_line(line):
332
+ line2 = line.encode('utf-8', errors='ignore')
333
+ sys.stdout.write(f'@@>>@@ line skipped: {line2}' + '\n')
334
+ continue
335
+
336
+ # it has text, so add it to the buffer and do the callback if defined
337
+ lineno += 1
338
+ self._save_line(line, lineno, lines, fp, print_cb, use_raw_log, quiet)
339
+
340
+ # at this point: chunk was empty
341
+ if len(buffer) > 0:
342
+ buff_lines = buffer.split('\n')
343
+ for line in buff_lines:
344
+ lineno += 1
345
+ self._save_line(line, lineno, lines, fp, print_cb, use_raw_log, quiet)
346
+
347
+ if fp:
348
+ fp.flush()
349
+ fp.close()
350
+
351
+ rc += proc.wait()
352
+
353
+ if proc.stdout:
354
+ self.stdout_flush()
355
+ proc.stdout.close()
356
+ if proc.stderr:
357
+ self.stderr_flush()
358
+ proc.stderr.close()
359
+
360
+ return rc, lines
361
+
362
+ # -------------------
363
+ ## skip lines with ansi escape sequeces, etc.
364
+ # @param line the line to check
365
+ # @return True if line should be skipped, False otherwise
366
+ def _skip_line(self, line: str) -> bool:
367
+ if line == '\x1b[?25l': # hide mouse cursor
368
+ return True
369
+ if line == '\x1b[?25h': # hide mouse cursor
370
+ return True
371
+ if re.search(r'^\x1b.*\d+%', line): # skip percent lines
372
+ return True
373
+
374
+ if re.search(r'[\u2580-\u259F]', line):
375
+ return True
376
+
377
+ # Catch carriage returns (common in curl/wget across Linux/macOS/MSYS2)
378
+ if '\r' in line:
379
+ return True
380
+
381
+ # catch ANSI Escape Sequences (used for colors/moving cursors in bash/ps1)
382
+ # This strips \x1b[..., \033[..., etc.
383
+ # ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
384
+ # cleaned_line = ansi_escape.sub('', line)
385
+
386
+ # TODO fix; causes problems with coverage reports that has % in them
387
+ # e.g. functions: 100.0% (2 out of 2)
388
+ # Match progress percentages (e.g., 10%, 10.5%)
389
+ # if re.search(r'\d+(?:\.\d+)?%', cleaned_line):
390
+ # return True
391
+
392
+ # # Match interactive spinners (\ | / -)
393
+ # if re.search(r'(?:[\s\[\(\]{}]|^)([\\/|-]+)(?:[\s\]\)\}{}]|$)', cleaned_line):
394
+ # return True
395
+
396
+ return False
397
+
398
+ # -------------------
399
+ ## save the current line (if it's not empty)
400
+ # @param line the line to save
401
+ # @param lineno the line#
402
+ # @param lines the current list of lines
403
+ # @param fp file pointer to write line to; default is None
404
+ # @param print_cb callback function used to print output; default is None
405
+ # @param use_raw_log no prefix on line
406
+ # @param quiet if True, don't print to stdout, but still add to returned line
407
+ # @return None
408
+ def _save_line(self, line, lineno, lines, fp, print_cb, use_raw_log, quiet):
409
+ if not line:
410
+ # skip empty lines
411
+ return
412
+
413
+ # always save the line
414
+ lines.append(line)
415
+
416
+ if quiet:
417
+ # caller doesn't want it to stdout, fp or print_cb
418
+ return
419
+
420
+ if use_raw_log:
421
+ line_to_save = line
422
+ else:
423
+ line_to_save = f' -- {line}'
424
+ line_to_save = line_to_save.replace('←', '\x1b')
425
+ sys.stdout.write(line_to_save + '\n')
426
+ self.stdout_flush()
427
+
428
+ if fp:
429
+ fp.write(line_to_save)
430
+ fp.write('\n')
431
+
432
+ if print_cb:
433
+ print_cb(lineno, line)
434
+
435
+ # --------------------
436
+ ## flush for stdout
437
+ # @param force (optional) if True, do the buffer flush even if not msys2
438
+ def stdout_flush(self, force=False):
439
+ sys.stdout.flush()
440
+ if force or self._os_name == 'msys2':
441
+ if hasattr(sys.stdout, 'buffer'):
442
+ sys.stdout.buffer.flush()
443
+
444
+ # --------------------
445
+ ## flush for stderr
446
+ # @param force (optional) if True, do the buffer flush even if not msys2
447
+ def stderr_flush(self, force=False):
448
+ sys.stderr.flush()
449
+ if force or self._os_name == 'msys2':
450
+ if hasattr(sys.stderr, 'buffer'):
451
+ sys.stderr.buffer.flush()
452
+
453
+ # --------------------
454
+ ## convert a windows path to posix i.e. forward slashes
455
+ # @param path the path to fix
456
+ # @param force force the conversion no matter what the OS is
457
+ # @return the unfixed path
458
+ def xlat_to_win(self, path, force=False):
459
+ path = os.path.expanduser(path)
460
+ if force or self._os_name == 'msys2':
461
+ # assumes there is only one character and it is for a drive letter
462
+ # can only be done at the start of the path
463
+ m = re.search(r'^/(.)/(.*)', path)
464
+ if m:
465
+ drive = m.group(1).upper()
466
+ path = f'{drive}:/{m.group(2)}'
467
+ path = path.replace('/', '\\')
468
+ path = path.replace('//', '\\')
469
+ return path
470
+
471
+ # --------------------
472
+ ## convert a windows path to posix i.e. forward slashes
473
+ # @param path the path to fix
474
+ # @param force force the conversion no matter what the OS is
475
+ # @return the unfixed path
476
+ def xlat_to_posix(self, path, force=False):
477
+ path = os.path.expanduser(path)
478
+ if force or self._os_name == 'msys2':
479
+ m = re.search(r'^(.):(.*)', path)
480
+ if m:
481
+ drive = m.group(1).lower()
482
+ path = f'/{drive}{m.group(2)}'
483
+ path = path.replace('\\', '/')
484
+ path = path.replace('//', '/')
485
+ return path
486
+
487
+ # -------------------
488
+ ## convert a path to relative to home
489
+ # @param path the path to fix
490
+ # @return the relative path
491
+ def xlat_dir_to_relative(self, path):
492
+ path = self.xlat_to_posix(path)
493
+ path = path.replace(self.xlat_to_posix(os.path.expanduser('~')), '~')
494
+ path = path.replace('\\', '/')
495
+ return path
496
+
497
+ # -------------------
498
+ ## current repo directory converted to posix
499
+ # @return the repo dir path
500
+ @property
501
+ def curr_repo_dir(self):
502
+ if self.ut_curr_dir is None:
503
+ repo_dir = os.getcwd()
504
+ repo_dir = repo_dir.replace('\\', '/')
505
+ repo_dir = repo_dir.replace('//', '/')
506
+ else:
507
+ repo_dir = self.ut_curr_dir
508
+ return repo_dir
509
+
510
+ # -------------------
511
+ ## current repo name
512
+ # @return the repo name
513
+ @property
514
+ def curr_repo_name(self):
515
+ return os.path.basename(os.path.normpath(os.getcwd()))
516
+
517
+ # --------------------
518
+ ## abort the current script.
519
+ # Note: do not use logging here since it may fail to write correctly
520
+ #
521
+ # @param msg (optional) message to display
522
+ # @return does not return
523
+ def abort(self, msg='abort occurred, exiting'):
524
+ sys.stdout.write('\n')
525
+ sys.stdout.write(f'ABRT {msg}')
526
+ self.stdout_flush()
527
+ self.stderr_flush()
528
+ sys.exit(1)
529
+
530
+ # # -------------------
531
+ # class ExcpAbort(Exception):
532
+ # def __init__(self, message):
533
+ # super().__init__(message)
534
+ # self.level = 'line'
535
+ # self.detail = None
536
+
537
+ # def abort(self, msg='ABRT session aborted', level='line'):
538
+ # excp = ExcpAbort(msg)
539
+ # excp.level = level
540
+ # raise excp
541
+
542
+ # === UT functions
543
+
544
+ # -------------------
545
+ ## get ut_mode
546
+ # @return ut_mode (true/false)
547
+ @property
548
+ def ut_mode(self):
549
+ return self._ut_mode
550
+
551
+ # -------------------
552
+ ## set for ut_mode
553
+ # @param val new value to use
554
+ # @return None
555
+ @ut_mode.setter
556
+ def ut_mode(self, val):
557
+ self._ut_mode = val
558
+
559
+ # -------------------
560
+ ## get set os_name
561
+ # @param ut_os_name new value to use
562
+ # @return ut_mode (true/false)
563
+ def ut_info(self, ut_os_name):
564
+ self._os_name = ut_os_name
565
+ self._set_all_data()
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyosal
3
+ Version: 0.1.0
4
+ Summary: python Operating System Abstract Layer
5
+ Author-email: "J. Arrizza" <cppgent0@gmail.com>
6
+ Maintainer-email: "J. Arrizza" <cppgent0@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Website, https://arrizza.com/pyosal
9
+ Project-URL: Source, https://bitbucket.org/arrizza-public/pyosal/src/master
10
+ Project-URL: Download, https://bitbucket.org/arrizza-public/pyosal/get/master.zip
11
+ Keywords: SHOULD-NOT-MATCH,SHOULD-NOT-MATCH,SHOULD-NOT-MATCH
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Topic :: Utilities
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.txt
21
+ Dynamic: license-file
22
+
23
+ * website: [Website](https://arrizza.com/TODO.html)
24
+ * installation: [Common Setup](https://arrizza.com/setup-common.html)
25
+
26
+ ## Summary
27
+
28
+ ***Under Construction***
@@ -0,0 +1,13 @@
1
+ LICENSE.txt
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.cfg
6
+ src/pyosal/__init__.py
7
+ src/pyosal/constants.py
8
+ src/pyosal/constants_version.py
9
+ src/pyosal/pyosal.py
10
+ src/pyosal.egg-info/PKG-INFO
11
+ src/pyosal.egg-info/SOURCES.txt
12
+ src/pyosal.egg-info/dependency_links.txt
13
+ src/pyosal.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pyosal