Coverage for pygeodesy/internals.py: 88%
298 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-22 18:16 -0400
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-22 18:16 -0400
2# -*- coding: utf-8 -*-
4u'''Mostly INTERNAL functions, except L{machine}, L{print_} and L{printf}.
5'''
6# from pygeodesy.basics import isiterablen, ubstr # _MODS
7# from pygeodesy.errors import _AttributeError, _error_init, _UnexpectedError, _xError2 # _MODS
8from pygeodesy.interns import NN, _BAR_, _COLON_, _DASH_, _DOT_, _ELLIPSIS_, _EQUALSPACED_, \
9 _immutable_, _NL_, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, \
10 _QUOTE2_, _s_, _SPACE_, _sys, _UNDER_, _utf_8_
11from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used!
12# from pygeodesy.streprs import anstr, pairs, unstr # _MODS
14# import os # _MODS
15# import os.path # _MODS
16# import sys as _sys # from .interns
18_0_0 = 0.0 # PYCHOK in .basics, .constants
19_arm64_ = 'arm64'
20_iOS_ = 'iOS'
21_macOS_ = 'macOS'
22_SIsecs = 'fs', 'ps', 'ns', 'us', 'ms', 'sec' # reversed
23_Windows_ = 'Windows'
26def _DUNDER_nameof(inst, *dflt):
27 '''(INTERNAL) Get the DUNDER C{.__name__} attr.
28 '''
29 try:
30 return inst.__name__
31 except AttributeError:
32 pass
33 return dflt[0] if dflt else inst.__class__.__name__
36def _DUNDER_nameof_(*names__): # in .errors._IsnotError
37 '''(INTERNAL) Yield the _DUNDER_nameof or name.
38 '''
39 return map(_DUNDER_nameof, names__, names__)
42def _Property_RO(method):
43 '''(INTERNAL) Can't I{recursively} import L{props.property_RO}.
44 '''
45 name = _DUNDER_nameof(method)
47 def _del(inst, attr): # PYCHOK no cover
48 delattr(inst, attr) # force error
50 def _get(inst, **unused): # PYCHOK 2 vs 3 args
51 try: # to get the cached value immediately
52 v = inst.__dict__[name]
53 except (AttributeError, KeyError):
54 # cache the value in the instance' __dict__
55 inst.__dict__[name] = v = method(inst)
56 return v
58 def _set(inst, val): # PYCHOK no cover
59 setattr(inst, name, val) # force error
61 return property(_get, _set, _del)
64class _MODS_Base(object):
65 '''(INTERNAL) Base-class for C{lazily._ALL_MODS}.
66 '''
67 def __delattr__(self, attr): # PYCHOK no cover
68 self.__dict__.pop(attr, None)
70 def __setattr__(self, attr, value): # PYCHOK no cover
71 e = _MODS.errors
72 t = _EQUALSPACED_(self._DOT_(attr), repr(value))
73 raise e._AttributeError(_immutable_, txt=t)
75 @_Property_RO
76 def basics(self):
77 '''Get module C{pygeodesy.basics}, I{once}.
78 '''
79 from pygeodesy import basics as b # DON'T _lazy_import2
80 return b
82 @_Property_RO
83 def bits_machine2(self):
84 '''Get platform 2-list C{[bits, machine]}, I{once}.
85 '''
86 import platform as p
88 m = p.machine() # ARM64, arm64, x86_64, iPhone13,2, etc.
89 m = m.replace(_COMMA_, _UNDER_)
90 if m.lower() == 'x86_64': # PYCHOK on Intel or Rosetta2 ...
91 v = p.mac_ver()[0] # ... and only on macOS ...
92 if v and _version2(v) > (10, 15): # ... 11+ aka 10.16
93 # <https://Developer.Apple.com/forums/thread/659846>
94 # _sysctl_uint('hw.optional.arm64') and \
95 if _sysctl_uint('sysctl.proc_translated'):
96 m = _UNDER_(_arm64_, m) # Apple Si emulating Intel x86-64
97 return [p.architecture()[0], # bits
98 m] # arm64, arm64_x86_64, x86_64, etc.
100 @_Property_RO
101 def ctypes3(self):
102 '''Get 3-tuple C{(ctypes.CDLL, ._dlopen, .util.findlibrary)}, I{once}.
103 '''
104 if _ismacOS():
105 from ctypes import CDLL, DEFAULT_MODE, _dlopen
107 def dlopen(name):
108 return _dlopen(name, DEFAULT_MODE)
110 else: # PYCHOK no cover
111 from ctypes import CDLL
112 dlopen = _passarg
114 from ctypes.util import find_library
115 return CDLL, dlopen, find_library
117 @_Property_RO
118 def ctypes5(self):
119 '''Get 5-tuple C{(ctypes.byref, .c_char_p, .c_size_t, .c_uint, .sizeof)}, I{once}.
120 '''
121 from ctypes import byref, c_char_p, c_size_t, c_uint, sizeof # get_errno
122 return byref, c_char_p, c_size_t, c_uint, sizeof
124 def _DOT_(self, name): # PYCHOK no cover
125 return _DOT_(self.name, name)
127 @_Property_RO
128 def errors(self):
129 '''Get module C{pygeodesy.errors}, I{once}.
130 '''
131 from pygeodesy import errors as e # DON'T _lazy_import2
132 return e
134 @_Property_RO
135 def inspect(self): # in .basics
136 '''Get module C{inspect}, I{once}.
137 '''
138 import inspect as i
139 return i
141 def ios_ver(self):
142 '''Mimick C{platform.xxx_ver} for C{iOS}.
143 '''
144 try: # Pythonista only
145 from platform import iOS_ver
146 return iOS_ver()
147 except (AttributeError, ImportError):
148 return NN, (NN, NN, NN), NN
150 @_Property_RO
151 def libc(self):
152 '''Load C{libc.dll|dylib}, I{once}.
153 '''
154 return _load_lib('libc')
156 @_Property_RO
157 def name(self):
158 '''Get this name (C{str}).
159 '''
160 return _DUNDER_nameof(self.__class__)
162 @_Property_RO
163 def nix2(self): # PYCHOK no cover
164 '''Get Linux 2-list C{[distro, version]}, I{once}.
165 '''
166 import platform as p
168 n, v = p.uname()[0], NN
169 if n.lower() == 'linux':
170 try: # use distro only for Linux, not macOS, etc.
171 import distro # <https://PyPI.org/project/distro>
172 _a = _MODS.streprs.anstr
173 v = _a(distro.version()) # first
174 n = _a(distro.id()) # .name()?
175 except (AttributeError, ImportError):
176 pass # v = str(_0_0)
177 n = n.capitalize()
178 return n, v
180 def nix_ver(self): # PYCHOK no cover
181 '''Mimick C{platform.xxx_ver} for C{*nix}.
182 '''
183 _, v = _MODS.nix2
184 t = _version2(v, n=3) if v else (NN, NN, NN)
185 return v, t, machine()
187 @_Property_RO
188 def os(self):
189 '''Get module C{os}, I{once}.
190 '''
191 import os as o
192 import os.path
193 return o
195 @_Property_RO
196 def osversion2(self):
197 '''Get 2-list C{[OS, release]}, I{once}.
198 '''
199 import platform as p
201 _Nix, _ = _MODS.nix2
202 # - mac_ver() returns ('10.12.5', ..., 'x86_64') on
203 # macOS and ('10.3.3', ..., 'iPad4,2') on iOS
204 # - win32_ver is ('XP', ..., 'SP3', ...) on Windows XP SP3
205 # - platform() returns 'Darwin-16.6.0-x86_64-i386-64bit'
206 # on macOS and 'Darwin-16.6.0-iPad4,2-64bit' on iOS
207 # - sys.platform is 'darwin' on macOS, 'ios' on iOS,
208 # 'win32' on Windows and 'cygwin' on Windows/Gygwin
209 # - distro.id() and .name() return 'Darwin' on macOS
210 for n, v in ((_iOS_, _MODS.ios_ver),
211 (_macOS_, p.mac_ver),
212 (_Windows_, p.win32_ver),
213 (_Nix, _MODS.nix_ver),
214 ('Java', p.java_ver),
215 ('uname', p.uname)):
216 v = v()[0]
217 if v and n:
218 break
219 else:
220 n = v = NN # XXX AssertioError?
221 return [n, v]
223 @_Property_RO
224 def _Popen_kwds2(self):
225 '''(INTERNAL) Get C{subprocess.Popen} and C{-kwds}.
226 '''
227 import subprocess as _sub
228 kwds = dict(creationflags=0, # executable=sys.executable, shell=True,
229 stdin=_sub.PIPE, stdout=_sub.PIPE, stderr=_sub.STDOUT)
230 if _sys.version_info[:2] > (3, 6):
231 kwds.update(text=True)
232 return _sub.Popen, kwds
234 @_Property_RO
235 def Pythonarchine(self):
236 '''Get 3- or 4-list C{[PyPy, Python, bits, machine]}, I{once}.
237 '''
238 v = _sys.version
239 l3 = [_Python_(v)] + self.bits_machine2
240 pypy = _PyPy__(v)
241 if pypy: # PYCHOK no cover
242 l3.insert(0, pypy)
243 return l3
245 @_Property_RO
246 def streprs(self):
247 '''Get module C{pygeodesy.streprs}, I{once}.
248 '''
249 from pygeodesy import streprs as s # DON'T _lazy_import2
250 return s
252 @_Property_RO
253 def sys_version_info2(self):
254 '''Get C{sys.version_inf0[:2], I{once}.
255 '''
256 return _sys.version_info[:2]
258 @_Property_RO
259 def version(self):
260 '''Get pygeodesy version, I{once}.
261 '''
262 from pygeodesy import version as v
263 return v
265_MODS = _MODS_Base() # PYCHOK overwritten by .lazily
268def _caller3(up, base=True): # in .lazily, .named
269 '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)}
270 for the caller B{C{up}} frames back in the Python call stack.
272 @kwarg base: Use C{B{base}=False} for the fully-qualified file
273 name, otherwise the base (module) name (C{bool}).
274 '''
275 f = None
276 _f = _MODS.os.path.basename if base else _passarg
277 try:
278 f = _sys._getframe(up + 1) # == inspect.stack()[up + 1][0]
279 t = _MODS.inspect.getframeinfo(f)
280 t = t.function, _f(t.filename), t.lineno
281# or ...
282 # f = _sys._getframe(up + 1)
283 # c = f.f_code
284 # t = (c.co_name, # caller name
285 # _f(c.co_filename), # file name .py
286 # f.f_lineno) # line number
287# or ...
288 # t = _MODS.inspect.stack()[up + 1] # (frame, filename, lineno, function, ...)
289 # t = t[3], _f(t[1]), t[2]
290 except (AttributeError, IndexError, ValueError):
291 # sys._getframe(1) ... 'importlib._bootstrap' line 1032,
292 # may throw a ValueError('call stack not deep enough')
293 t = NN, NN, 0
294 finally:
295 del f # break ref cycle
296 return t
299def _enquote(strs, quote=_QUOTE2_, white=NN): # in .basics, .solveBase
300 '''(INTERNAL) Enquote a string containing whitespace or replace
301 whitespace by C{white} if specified.
302 '''
303 if strs:
304 t = strs.split()
305 if len(t) > 1:
306 strs = white.join(t if white else (quote, strs, quote))
307 return strs
310def _fper(p, q, per=100.0, prec=1):
311 '''Format a percentage C{B{p} * B{per} / B{q}} (C{str}).
312 '''
313 return '%.*f%%' % (prec, (float(p) * per / float(q)))
316_getenv = _MODS.os.getenv # PYCHOK in .lazily, ...
319def _getPYGEODESY(which, dflt=NN):
320 '''(INTERNAL) Return an C{PYGEODESY_...} ENV value or C{dflt}.
321 '''
322 return _getenv(_PYGEODESY(which), dflt)
325def _headof(name):
326 '''(INTERNAL) Get the head name of qualified C{name} or the C{name}.
327 '''
328 i = name.find(_DOT_)
329 return name if i < 0 else name[:i]
332# def _is(a, b): # PYCHOK no cover
333# '''(INTERNAL) C{a is b}? in C{PyPy}
334# '''
335# return (a == b) if _isPyPy() else (a is b)
338def _isAppleSi():
339 '''(INTERNAL) Is this C{macOS on Apple Silicon}? (C{bool})
340 '''
341 return _ismacOS() and machine().startswith(_arm64_)
344def _is_DUNDER_main(name):
345 '''(INTERNAL) Return C{bool(name == '__main__')}.
346 '''
347 return name == '__main__'
350def _isiOS(): # in test/bases
351 '''(INTERNAL) Is this C{iOS}? (C{bool})
352 '''
353 return _MODS.osversion2[0] is _iOS_
356def _ismacOS(): # in test/bases
357 '''(INTERNAL) Is this C{macOS}? (C{bool})
358 '''
359 return _sys.platform[:6] == 'darwin' and \
360 _MODS.osversion2[0] is _macOS_ # and _MODS.os.name == 'posix'
363def _isNix(): # in test/bases
364 '''(INTERNAL) Is this a C{Linux} distro? (C{str} or L{NN})
365 '''
366 return _MODS.nix2[0]
369def _isPyChecker():
370 '''(INTERNAL) Is C{PyChecker} running? (C{bool}).
371 '''
372 # .../pychecker/checker.py --limit 0 --stdlib pygeodesy/<mod>/<name>.py
373 return _sys.argv[0].endswith('/pychecker/checker.py')
376def _isPyPy(): # in test/bases
377 '''(INTERNAL) Is this C{PyPy}? (C{bool})
378 '''
379 # platform.python_implementation() == 'PyPy'
380 return _MODS.Pythonarchine[0].startswith(_PyPy__)
383def _isWindows(): # in test/bases
384 '''(INTERNAL) Is this C{Windows}? (C{bool})
385 '''
386 return _sys.platform[:3] == 'win' and \
387 _MODS.osversion2[0] is _Windows_
390def _load_lib(name):
391 '''(INTERNAL) Load a C{dylib}, B{C{name}} must startwith('lib').
392 '''
393 # macOS 11+ (aka 10.16) no longer provides direct loading of
394 # system libraries. As a result, C{ctypes.util.find_library}
395 # will not find any library, unless previously installed by a
396 # low-level dlopen(name) call (with the library base C{name}).
397 CDLL, dlopen, find_lib = _MODS.ctypes3
399 ns = find_lib(name), name
400 if dlopen is not _passarg: # _ismacOS()
401 ns += (_DOT_(name, 'dylib'),
402 _DOT_(name, 'framework'), _MODS.os.path.join(
403 _DOT_(name, 'framework'), name))
404 for n in ns:
405 try:
406 if n and dlopen(n): # pre-load handle
407 lib = CDLL(n) # == ctypes.cdll.LoadLibrary(n)
408 if lib._name: # has a qualified name
409 return lib
410 except (AttributeError, OSError):
411 pass
413 return None # raise OSError
416def machine():
417 '''Return standard C{platform.machine}, but distinguishing Intel I{native}
418 from Intel I{emulation} on Apple Silicon (on macOS only).
420 @return: Machine C{'arm64'} for Apple Silicon I{native}, C{'x86_64'}
421 for Intel I{native}, C{"arm64_x86_64"} for Intel I{emulation},
422 etc. (C{str} with C{comma}s replaced by C{underscore}s).
423 '''
424 return _MODS.bits_machine2[1]
427def _name_version(pkg):
428 '''(INTERNAL) Return C{pskg.__name__ + ' ' + .__version__}.
429 '''
430 return _SPACE_(pkg.__name__, pkg.__version__)
433def _osversion2(sep=NN): # in .lazily, test/bases.versions
434 '''(INTERNAL) Get the O/S name and release as C{2-list} or C{str}.
435 '''
436 l2 = _MODS.osversion2
437 return sep.join(l2) if sep else l2 # 2-list()
440def _passarg(arg):
441 '''(INTERNAL) Helper, no-op.
442 '''
443 return arg
446def _passargs(*args):
447 '''(INTERNAL) Helper, no-op.
448 '''
449 return args
452def _plural(noun, n, nn=NN):
453 '''(INTERNAL) Return C{noun}['s'] or C{NN}.
454 '''
455 return NN(noun, _s_) if n > 1 else (noun if n else nn)
458def _popen2(cmd, stdin=None): # in .mgrs, .solveBase, .testMgrs
459 '''(INTERNAL) Invoke C{B{cmd} tuple} and return 2-tuple C{(std, status)}
460 with all C{stdout/-err} output, I{stripped} and C{int} exit status.
461 '''
462 _Popen, kwds = _MODS._Popen_kwds2
463 p = _Popen(cmd, **kwds) # PYCHOK kwArgs
464 r = p.communicate(stdin)[0] # stdout + NL + stderr
465 return _MODS.basics.ub2str(r).strip(), p.returncode
468def print_(*args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): # PYCHOK no cover
469 '''Python 3+ C{print}-like formatting and printing.
471 @arg args: Values to be converted to C{str} and joined by B{C{sep}},
472 all positional.
474 @see: Function L{printf} for further details.
475 '''
476 return printf(NN, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
479def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds):
480 '''C{Printf-style} and Python 3+ C{print}-like formatting and printing.
482 @arg fmt: U{Printf-style<https://Docs.Python.org/3/library/stdtypes.html#
483 printf-style-string-formatting>} format specification (C{str}).
484 @arg args: Arguments to be formatted (any C{type}, all positional).
485 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments
486 C{B{nl}=0} for the number of leading blank lines (C{int}), C{B{nt}=0}
487 the number of trailing blank lines (C{int}), C{B{prefix}=NN} to be
488 inserted before the formatted text (C{str}) and Python 3+ C{print}
489 keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and C{B{flush}}.
490 Any remaining C{B{kwds}} are C{printf-style} name-value pairs to be
491 formatted, I{iff no B{C{args}} are present} using C{B{prec}=6} for
492 the number of decimal digits (C{int}).
494 @return: Number of bytes written.
495 '''
496 b, e, f, fl, p, s, kwds = _print7(**nl_nt_prec_prefix__end_file_flush_sep__kwds)
497 try:
498 if args:
499 t = (fmt % args) if fmt else s.join(map(str, args))
500 elif kwds:
501 t = (fmt % kwds) if fmt else s.join(
502 _MODS.streprs.pairs(kwds, prec=p))
503 else:
504 t = fmt
505 except Exception as x:
506 _E, s = _MODS.errors._xError2(x)
507 unstr = _MODS.streprs.unstr
508 t = unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
509 raise _E(s, txt=t, cause=x)
510 try:
511 n = f.write(NN(b, t, e))
512 except UnicodeEncodeError: # XXX only Windows
513 t = t.replace('\u2032', _QUOTE1_).replace('\u2033', _QUOTE2_)
514 n = f.write(NN(b, t, e))
515 if fl: # PYCHOK no cover
516 f.flush()
517 return n
520def _print7(nl=0, nt=0, prec=6, prefix=NN, sep=_SPACE_, file=_sys.stdout,
521 end=_NL_, flush=False, **kwds):
522 '''(INTERNAL) Unravel the C{printf} and remaining keyword arguments.
523 '''
524 if nl > 0:
525 prefix = NN(_NL_ * nl, prefix)
526 if nt > 0:
527 end = NN(end, _NL_ * nt)
528 return prefix, end, file, flush, prec, sep, kwds
531def _PYGEODESY(which, i=0):
532 '''(INTERNAL) Return an ENV C{str} C{PYGEODESY_...}.
533 '''
534 try:
535 w = which.__name__.lstrip(_UNDER_)[i:]
536 except AttributeError:
537 w = which
538 return _UNDER_(_pygeodesy_, w).upper()
541def _Pythonarchine(sep=NN): # in .lazily, test/bases versions
542 '''(INTERNAL) Get PyPy and Python versions, bits and machine as C{3- or 4-list} or C{str}.
543 '''
544 l3 = _MODS.Pythonarchine
545 return sep.join(l3) if sep else l3 # 3- or 4-list
548def _secs2str(secs): # in .geoids, ../test/bases
549 '''Convert a time in C{secs} to C{str}.
550 '''
551 if secs < 100.0: # _100_0
552 unit = len(_SIsecs) - 1
553 while 0 < secs < 1 and unit > 0:
554 secs *= 1e3 # _1000_0
555 unit -= 1
556 t = '%.3f %s' % (secs, _SIsecs[unit])
557 else:
558 m, s = divmod(secs, 60)
559 if m < 60:
560 t = '%d:%06.3f' % (int(m), s)
561 else:
562 h, m = divmod(int(m), 60)
563 t = '%d:%02d:%06.3f' % (h, m, s)
564 return t
567def _sizeof(obj, deep=True):
568 '''(INTERNAL) Recursively size an C{obj}ect.
570 @kwarg deep: If C{True}, include the size of all
571 C{.__dict__.values()} (C{bool}).
573 @return: The C{obj} size in bytes (C{int}), ignoring
574 class attributes and counting instances only
575 once or C{None}.
577 @note: With C{PyPy}, the returned size is always C{None}.
578 '''
579 try:
580 _zB = _sys.getsizeof
581 _zD = _zB(None) # some default
582 except TypeError: # PyPy3.10
583 return None
585 b = _MODS.basics
586 _isiterablen = b.isiterablen
587 _Str_Bytes = b._Strs + b._Bytes # + (range, map)
589 def _zR(s, iterable):
590 z, _s = 0, s.add
591 for o in iterable:
592 i = id(o)
593 if i not in s:
594 _s(i)
595 z += _zB(o, _zD)
596 if isinstance(o, dict):
597 z += _zR(s, o.keys())
598 z += _zR(s, o.values())
599 elif _isiterablen(o) and not \
600 isinstance(o, _Str_Bytes):
601 z += _zR(s, o)
602 elif deep:
603 try: # size instance' attr values only
604 z += _zR(s, o.__dict__.values())
605 except AttributeError: # None, int, etc.
606 pass
607 return z
609 return _zR(set(), (obj,))
612def _sysctl_uint(name):
613 '''(INTERNAL) Get an unsigned int sysctl item by name, use on macOS ONLY!
614 '''
615 libc = _MODS.libc
616 if libc: # <https://StackOverflow.com/questions/759892/python-ctypes-and-sysctl>
617 byref, char_p, size_t, uint, sizeof = _MODS.ctypes5
618 n = name if str is bytes else bytes(name, _utf_8_) # PYCHOK isPython2 = str is bytes
619 u = uint(0)
620 z = size_t(sizeof(u))
621 r = libc.sysctlbyname(char_p(n), byref(u), byref(z), None, size_t(0))
622 else: # couldn't find or load 'libc'
623 r = -2
624 return int(r if r else u.value) # -1 ENOENT error, -2 no libc
627def _tailof(name):
628 '''(INTERNAL) Get the base name of qualified C{name} or the C{name}.
629 '''
630 i = name.rfind(_DOT_) + 1
631 return name[i:] if i > 0 else name
634def _under(name): # PYCHOK in .datums, .auxilats, .ups, .utm, .utmupsBase, ...
635 '''(INTERNAL) Prefix C{name} with an I{underscore}.
636 '''
637 return name if name.startswith(_UNDER_) else NN(_UNDER_, name)
640def _usage(file_py, *args, **opts_help): # in .etm, .geodesici
641 '''(INTERNAL) Build "usage: python -m ..." cmd line for module B{C{file_py}}.
642 '''
643 if opts_help:
645 def _help(alts=(), help=NN, **unused):
646 if alts and help:
647 h = NN(help, _SPACE_).lstrip(_DASH_)
648 for a in alts:
649 if a.startswith(h):
650 return NN(_DASH_, a),
652 def _opts(opts=NN, alts=(), **unused):
653 # opts='T--v-C-R meter-c|i|n|o'
654 d, fmt = NN, _MODS.streprs.Fmt.SQUARE
655 for o in (opts + _BAR_(*alts)).split(_DASH_):
656 if o:
657 yield fmt(NN(d, _DASH_, o.replace(_BAR_, ' | -')))
658 d = NN
659 else:
660 d = _DASH_
662 args = _help(**opts_help) or (tuple(_opts(**opts_help)) + args)
664 u = _COLON_(_DUNDER_nameof(_usage)[1:], NN)
665 return _SPACE_(u, *_usage_argv(file_py, *args))
668def _usage_argv(argv0, *args):
669 '''(INTERNAL) Return 3-tuple C{(python, '-m', module, *args)}.
670 '''
671 o = _MODS.os
672 m = o.path.dirname(argv0)
673 m = m.replace(o.getcwd(), _ELLIPSIS_) \
674 .replace(o.sep, _DOT_).strip()
675 b = o.path.basename(argv0)
676 b, x = o.path.splitext(b)
677 if x == '.py' and not _is_DUNDER_main(b):
678 m = _DOT_(m or _pygeodesy_, b)
679 p = NN(_python_, _sys.version_info[0])
680 return (p, '-m', _enquote(m)) + args
683def _version2(version, n=2):
684 '''(INTERNAL) Split C{B{version} str} into a C{1-, 2- or 3-tuple} of C{int}s.
685 '''
686 t = _version_ints(version.split(_DOT_, 2))
687 if len(t) < n:
688 t += (0,) * n
689 return t[:n]
692def _version_info(package): # in .Base.karney, .basics
693 '''(INTERNAL) Get the C{package.__version_info__} as a 2- or
694 3-tuple C{(major, minor, revision)} if C{int}s.
695 '''
696 try:
697 return _version_ints(package.__version_info__)
698 except AttributeError:
699 return _version2(package.__version__.strip(), n=3)
702def _version_ints(vs):
703 # helper for _version2 and _version_info above
705 def _ints(vs):
706 for v in vs:
707 try:
708 yield int(v.strip())
709 except (TypeError, ValueError):
710 pass
712 return tuple(_ints(vs))
715def _versions(sep=_SPACE_):
716 '''(INTERNAL) Get pygeodesy, PyPy and Python versions, bits, machine and OS as C{7- or 8-list} or C{str}.
717 '''
718 l7 = [_pygeodesy_, _MODS.version] + _Pythonarchine() + _osversion2()
719 return sep.join(l7) if sep else l7 # 5- or 6-list
722__all__ = tuple(map(_DUNDER_nameof, (machine, print_, printf)))
723__version__ = '24.10.20'
725if _is_DUNDER_main(__name__): # PYCHOK no cover
727 def _main():
728 from pygeodesy import _isfrozen, isLazy
730 print_(*(_versions(sep=NN) + ['_isfrozen', _isfrozen,
731 'isLazy', isLazy]))
733 _main()
735# **) MIT License
736#
737# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
738#
739# Permission is hereby granted, free of charge, to any person obtaining a
740# copy of this software and associated documentation files (the "Software"),
741# to deal in the Software without restriction, including without limitation
742# the rights to use, copy, modify, merge, publish, distribute, sublicense,
743# and/or sell copies of the Software, and to permit persons to whom the
744# Software is furnished to do so, subject to the following conditions:
745#
746# The above copyright notice and this permission notice shall be included
747# in all copies or substantial portions of the Software.
748#
749# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
750# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
751# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
752# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
753# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
754# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
755# OTHER DEALINGS IN THE SOFTWARE.