Coverage for pygeodesy / internals.py: 90%
278 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-08 14:37 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-08 14:37 -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, _ImmutableError, _UnexpectedError, \
8# _xError2, _xkwds_item2 # _MODS
9from pygeodesy.interns import _BAR_, _COLON_, _DASH_, _DMAIN_, _DOT_, _ELLIPSIS_, _NL_, NN, \
10 _NLATvar_, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, _QUOTE2_, \
11 _s_, _sys, _SPACE_, _UNDER_
12from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used!
13# from pygeodesy.streprs import anstr, pairs, unstr # _MODS
15# import os # _MODS
16# import os.path # _MODS
17# import sys as _sys # from .interns
19_0_0 = 0.0 # PYCHOK in .basics, .constants
20_100_0 = 100.0 # in .constants
21_arm64_ = 'arm64'
22_iOS_ = 'iOS'
23_macOS_ = 'macOS'
24_SIsecs = 'fs', 'ps', 'ns', 'us', 'ms', 'sec' # reversed
25_Windows_ = 'Windows'
28def typename(obj, *dflt):
29 '''Get the C{obj.__name__}, the C{dflt} or its outer C{type.__name__} or C{NN} (C{str}).
30 '''
31 try:
32 return obj.__name__
33 except (AttributeError, ImportError): # LazyImportError
34 pass
35 return dflt[0] if dflt else typename(type(obj), NN)
38def _Property_RO(method):
39 '''(INTERNAL) Can't import L{props.Property_RO}, I{recursively}.
40 '''
41 name = typename(method)
43 def _del(inst, *unused): # PYCHOK no cover
44 inst.__dict__.pop(name, None)
46 def _get(inst, *unused): # PYCHOK 2 vs 3 args
47 try: # to get the cached value immediately
48 v = inst.__dict__[name]
49 except (AttributeError, KeyError):
50 # cache the value in the instance' __dict__
51 inst.__dict__[name] = v = method(inst)
52 return v
54 def _set(inst, val): # PYCHOK no cover
55 setattr(inst, name, val) # force error
57 return property(_get, _set, _del)
60class _Enum(object): # in .elliptic, .utily
61 '''(INTERNAL) Enum-like, immutable items.
62 '''
63 # _ImmutableError = None
65 def __init__(self, **enums):
66 self.__dict__.update(enums)
67 # for item in enums.items():
68 # setattr(self, *item) # object.__setattr__
70 def __str__(self):
71 _unstr = _MODS.streprs.unstr
72 return _unstr(_Enum, **self.__dict__)
74 # def __delattr__(self, attr): # PYCHOK no cover
75 # raise _ImmutableError(self, attr) # _del_
77 # def __setattr__(self, attr, value): # PYCHOK no cover
78 # raise _ImmutableError(self, attr, value)
81class _MODS_Base(object):
82 '''(INTERNAL) Base-class for C{lazily._ALL_MODS}.
83 '''
84 def __delattr__(self, attr): # PYCHOK no cover
85 self.__dict__.pop(attr, None)
87 def __setattr__(self, attr, value): # PYCHOK no cover
88 raise _ImmutableError(self, attr, value)
90 @_Property_RO
91 def basics(self):
92 '''Get module C{pygeodesy.basics}, I{once}.
93 '''
94 from pygeodesy import basics as b # DON'T _lazy_import2
95 return b
97 @_Property_RO
98 def bits_machine2(self): # in test/bases.py
99 '''Get platform 2-list C{[bits, machine]}, I{once}.
100 '''
101 import platform as p
102 m = p.machine() # ARM64, arm64, x86_64, iPhone13,2, etc.
103 m = m.replace(_COMMA_, _UNDER_)
104 if m.lower() == 'x86_64': # PYCHOK on Intel or Rosetta2 ...
105 v = p.mac_ver()[0] # ... and only on macOS ...
106 if v and _version2(v) > (10, 15): # ... 11+ aka 10.16
107 # <https://Developer.Apple.com/forums/thread/659846>
108 # _sysctl_uint('hw.optional.arm64') and \
109 if _sysctl_uint('sysctl.proc_translated'):
110 m = _UNDER_(_arm64_, m) # Apple Si emulating Intel x86-64
111 return [p.architecture()[0], # bits
112 m] # arm64, arm64_x86_64, x86_64, etc.
114 @_Property_RO
115 def ctypes3(self):
116 '''Get C{ctypes.CDLL}, C{find_library} and C{dlopen}, I{once}.
117 '''
118 import ctypes as c
119 from ctypes.util import find_library as f
121 def dlopen(name): # on macOS only
122 return c._dlopen(name, c.DEFAULT_MODE)
124 return c.CDLL, f, (dlopen if _ismacOS() else None)
126 @_Property_RO
127 def errors(self):
128 '''Get module C{pygeodesy.errors}, I{once}.
129 '''
130 from pygeodesy import errors as e # DON'T _lazy_import2
131 return e
133 @_Property_RO
134 def inspect(self): # in .basics
135 '''Get module C{inspect}, I{once}.
136 '''
137 import inspect as i
138 return i
140 def ios_ver(self):
141 '''Mimick C{platform.xxx_ver} for C{iOS}.
142 '''
143 try: # Pythonista only
144 from platform import iOS_ver
145 t = iOS_ver()
146 except (AttributeError, ImportError):
147 t = NN, (NN, NN, NN), NN
148 return t
150 @_Property_RO
151 def name(self):
152 '''Get this name (C{str}).
153 '''
154 return typename(type(self))
156 @_Property_RO
157 def nix2(self): # PYCHOK no cover
158 '''Get Linux 2-tuple C{(distro, version)}, I{once}.
159 '''
160 from platform import uname
161 v, n = NN, uname()[0] # [0] == .system
162 if n.lower() == 'linux':
163 try: # use distro only on Linux, not macOS, etc.
164 import distro # <https://PyPI.org/project/distro>
165 _a = _MODS.streprs.anstr
166 v = _a(distro.version()) # first
167 n = _a(distro.id()) # .name()?
168 except (AttributeError, ImportError):
169 pass # v = str(_0_0)
170 n = n.capitalize()
171 return n, v
173 def nix_ver(self): # PYCHOK no cover
174 '''Mimick C{platform.xxx_ver} for C{*nix}.
175 '''
176 _, v = _MODS.nix2
177 t = _version2(v, n=3) if v else (NN, NN, NN)
178 return v, t, machine()
180 @_Property_RO
181 def os(self):
182 '''Get module C{os}, I{once}.
183 '''
184 import os as o
185 import os.path
186 return o
188 @_Property_RO
189 def osversion2(self):
190 '''Get 2-list C{[OS, release]}, I{once}.
191 '''
192 import platform as p
193 _Nix, _ = _MODS.nix2
194 # - mac_ver() returns ('10.12.5', ..., 'x86_64') on
195 # macOS and ('10.3.3', ..., 'iPad4,2') on iOS
196 # - win32_ver is ('XP', ..., 'SP3', ...) on Windows XP SP3
197 # - platform() returns 'Darwin-16.6.0-x86_64-i386-64bit'
198 # on macOS and 'Darwin-16.6.0-iPad4,2-64bit' on iOS
199 # - sys.platform is 'darwin' on macOS, 'ios' on iOS,
200 # 'win32' on Windows and 'cygwin' on Windows/Gygwin
201 # - distro.id() and .name() return 'Darwin' on macOS
202 for n, v in ((_iOS_, _MODS.ios_ver),
203 (_macOS_, p.mac_ver),
204 (_Windows_, p.win32_ver),
205 (_Nix, _MODS.nix_ver),
206# removed Py 3.15 ('Java', p.java_ver),
207 ('uname', p.uname)):
208 v = v()[0]
209 if v and n:
210 break
211 else:
212 n = v = NN # XXX AssertionError?
213 return [n, v]
215 @_Property_RO
216 def _Popen_kwds2(self):
217 '''(INTERNAL) Get C{subprocess.Popen} and C{-kwds}.
218 '''
219 import subprocess as s
220 kwds = dict(creationflags=0, # executable=sys.executable, shell=True,
221 stdin=s.PIPE, stdout=s.PIPE, stderr=s.STDOUT)
222 if _MODS.sys_version_info2 > (3, 6):
223 kwds.update(text=True)
224 return s.Popen, kwds
226 @_Property_RO
227 def Pythonarchine(self):
228 '''Get 3- or 4-list C{[PyPy, Python, bits, machine]}, I{once}.
229 '''
230 v = _sys.version
231 l3 = [_Python_(v)] + _MODS.bits_machine2
232 pypy = _PyPy__(v)
233 if pypy: # PYCHOK no cover
234 l3.insert(0, pypy)
235 return l3
237 @_Property_RO
238 def streprs(self):
239 '''Get module C{pygeodesy.streprs}, I{once}.
240 '''
241 from pygeodesy import streprs as s # DON'T _lazy_import2
242 return s
244 @_Property_RO
245 def sys_version_info2(self):
246 '''Get C{sys.version_inf0[:2], I{once}.
247 '''
248 return _sys.version_info[:2]
250 @_Property_RO
251 def version(self):
252 '''Get pygeodesy version, I{once}.
253 '''
254 from pygeodesy import version as v
255 return v
257_MODS = _MODS_Base() # PYCHOK overwritten by .lazily
260def _caller3(up, base=True): # in .lazily, .named
261 '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)}
262 for the caller B{C{up}} frames back in the Python call stack.
264 @kwarg base: Use C{B{base}=False} for the fully-qualified file
265 name, otherwise the base (module) name (C{bool}).
266 '''
267 f = None
268 _b = _MODS.os.path.basename if base else _passarg
269 try:
270 f = _sys._getframe(up + 1) # == inspect.stack()[up + 1][0]
271 t = _MODS.inspect.getframeinfo(f)
272 t = t.function, _b(t.filename), t.lineno
273# or ...
274 # f = _sys._getframe(up + 1)
275 # c = f.f_code
276 # t = (c.co_name, # caller name
277 # _b(c.co_filename), # file name .py
278 # f.f_lineno) # line number
279# or ...
280 # t = _MODS.inspect.stack()[up + 1] # (frame, filename, lineno, function, ...)
281 # t = t[3], _b(t[1]), t[2]
282 except (AttributeError, IndexError, ValueError):
283 # sys._getframe(1) ... 'importlib._bootstrap' line 1032,
284 # may throw a ValueError('call stack not deep enough')
285 t = NN, NN, 0
286 finally:
287 del f # break ref cycle
288 return t
291def _enquote(strs, quote=_QUOTE2_, white=NN): # in .basics, .solveBase
292 '''(INTERNAL) Enquote a string containing whitespace or replace
293 whitespace by C{white} if specified.
294 '''
295 if strs:
296 t = strs.split()
297 if len(t) > 1:
298 strs = white.join(t if white else (quote, strs, quote))
299 return strs
302def _envPYGEODESY(which, dflt=NN):
303 '''(INTERNAL) Return an C{PYGEODESY_...} ENV value or C{dflt}.
304 '''
305 return _getenv(_PYGEODESY_ENV(which), dflt)
308def _fper(p, q, per=_100_0, prec=1):
309 '''Format a percentage C{B{p} * B{per} / B{q}} (C{str}).
310 '''
311 return '%.*f%%' % (prec, (float(p) * per / float(q)))
314_getenv = _MODS.os.getenv # PYCHOK in .lazily, ... , pyrdnap
317def _headof(name):
318 '''(INTERNAL) Get the head name of qualified C{name} or the C{name}.
319 '''
320 i = name.find(_DOT_)
321 return name if i < 0 else name[:i]
324def _ImmutableError(*inst_attr_value):
325 '''(INTERNAL) Format an C{_ImmutableError}.
326 '''
327 return _MODS.errors._ImmutableError(*inst_attr_value)
330# def _is(a, b): # PYCHOK no cover
331# '''(INTERNAL) C{a is b}? in C{PyPy}
332# '''
333# return (a == b) if _isPyPy() else (a is b)
336def _isAppleSi(): # PYCHOK no cover
337 '''(INTERNAL) Is this C{macOS on Apple Silicon}? (C{bool})
338 '''
339 return _ismacOS() and machine().startswith(_arm64_)
342def _isiOS(): # in test/bases
343 '''(INTERNAL) Is this C{iOS}? (C{bool})
344 '''
345 return _MODS.osversion2[0] is _iOS_
348def _ismacOS(): # in test/bases
349 '''(INTERNAL) Is this C{macOS}? (C{bool})
350 '''
351 return _sys.platform[:6] == 'darwin' and \
352 _MODS.osversion2[0] is _macOS_ # and _MODS.os.name == 'posix'
355def _isNix(): # in test/bases
356 '''(INTERNAL) Is this a C{Linux} distro? (C{str} or L{NN})
357 '''
358 return _MODS.nix2[0]
361def _isPyChOK(): # PYCHOK no cover
362 '''(INTERNAL) Is C{PyChecker} running? (C{bool})
363 '''
364 # .../pychecker/checker.py --limit 0 --stdlib pygeodesy/<mod>/<name>.py
365 return _sys.argv[0].endswith('/pychecker/checker.py') or \
366 bool(_envPYGEODESY('PYCHOK'))
369def _isPyPy(): # in test/bases
370 '''(INTERNAL) Is this C{PyPy}? (C{bool})
371 '''
372 # platform.python_implementation() == 'PyPy'
373 return _MODS.Pythonarchine[0].startswith(_PyPy__)
376def _isWindows(): # in test/bases
377 '''(INTERNAL) Is this C{Windows}? (C{bool})
378 '''
379 return _sys.platform[:3] == 'win' and \
380 _MODS.osversion2[0] is _Windows_
383def _load_lib(name):
384 '''(INTERNAL) Load a C{dylib}, B{C{name}} must startwith('lib').
385 '''
386 CDLL, find_lib, dlopen = _MODS.ctypes3
387 ns = find_lib(name), name
388 if dlopen:
389 # macOS 11+ (aka 10.16) no longer provides direct loading of
390 # system libraries. As a result, C{ctypes.util.find_library}
391 # will not find any library, unless previously installed by a
392 # low-level dlopen(name) call (with the library base C{name}).
393 ns += (_DOT_(name, 'dylib'),
394 _DOT_(name, 'framework'), _MODS.os.path.join(
395 _DOT_(name, 'framework'), name))
396 else: # not macOS
397 dlopen = _passarg # no-op
399 for n in ns:
400 try:
401 if n and dlopen(n): # pre-load handle
402 lib = CDLL(n) # == ctypes.cdll.LoadLibrary(n)
403 if lib._name: # has a qualified name
404 return lib
405 except (AttributeError, OSError):
406 pass
408 return None # raise OSError
411def machine():
412 '''Return standard C{platform.machine}, but distinguishing Intel I{native}
413 from Intel I{emulation} on Apple Silicon (on macOS only).
415 @return: Machine C{'arm64'} for Apple Silicon I{native}, C{'x86_64'}
416 for Intel I{native}, C{"arm64_x86_64"} for Intel I{emulation},
417 etc. (C{str} with C{comma}s replaced by C{underscore}s).
418 '''
419 return _MODS.bits_machine2[1]
422def _name_version(pkg):
423 '''(INTERNAL) Return C{pkg.__name__ + ' ' + .__version__}.
424 '''
425 return _SPACE_(typename(pkg), pkg.__version__) # _DVERSION_
428def _osversion2(sep=NN): # in .lazily, test/bases.versions
429 '''(INTERNAL) Get the O/S name and release as C{2-list} or C{str}.
430 '''
431 l2 = _MODS.osversion2
432 return sep.join(l2) if sep else l2 # 2-list()
435def _passarg(arg):
436 '''(INTERNAL) Helper, no-op.
437 '''
438 return arg
441def _passargs(*args):
442 '''(INTERNAL) Helper, no-op.
443 '''
444 return args
447def _plural(noun, n, nn=NN):
448 '''(INTERNAL) Return C{noun}['s'] or C{NN}.
449 '''
450 return NN(noun, _s_) if n > 1 else (noun if n else nn)
453def _popen2(cmd, stdin=None): # in .mgrs, .solveBase, .testMgrs
454 '''(INTERNAL) Invoke C{B{cmd} tuple} and return 2-tuple C{(std, status)}
455 with all C{stdout/-err} output, I{stripped} and C{int} exit status.
456 '''
457 _Popen, kwds = _MODS._Popen_kwds2
458 p = _Popen(cmd, **kwds) # PYCHOK kwArgs
459 r = p.communicate(stdin)[0] # stdout + NL + stderr
460 return _MODS.basics.ub2str(r).strip(), p.returncode
463def _pregistry(registry):
464 '''(INTERNAL) Print all items of a C{registry}.
465 '''
466 t = [NN] + registry.toRepr(all=True, asorted=True).split(_NL_)
467 printf(_NLATvar_.join(i.strip(_COMMA_) for i in t))
470def print_(*args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): # PYCHOK no cover
471 '''Python 3+ C{print}-like formatting and printing.
473 @arg args: Values to be converted to C{str} and joined by B{C{sep}},
474 all positional.
476 @see: Function L{printf} for further details.
477 '''
478 return printf(NN, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
481def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds):
482 '''C{Printf-style} and Python 3+ C{print}-like formatting and printing.
484 @arg fmt: U{Printf-style<https://Docs.Python.org/3/library/stdtypes.html#
485 printf-style-string-formatting>} format specification (C{str}).
486 @arg args: Arguments to be formatted (any C{type}, all positional).
487 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments
488 C{B{nl}=0} for the number of leading blank lines (C{int}), C{B{nt}=0}
489 the number of trailing blank lines (C{int}), C{B{prefix}=NN} to be
490 inserted before the formatted text (C{str}) and Python 3+ C{print}
491 keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and C{B{flush}}.
492 Any remaining C{B{kwds}} are C{printf-style} name-value pairs to be
493 formatted, I{iff no B{C{args}} are present} using C{B{prec}=6} for
494 the number of decimal digits (C{int}).
496 @return: Number of bytes written.
497 '''
498 b, e, f, fl, p, s, kwds = _print7(**nl_nt_prec_prefix__end_file_flush_sep__kwds)
499 try:
500 if args:
501 t = (fmt % args) if fmt else s.join(map(str, args))
502 elif kwds:
503 t = (fmt % kwds) if fmt else s.join(
504 _MODS.streprs.pairs(kwds, prec=p))
505 else:
506 t = fmt
507 except Exception as x:
508 _Error, s = _MODS.errors._xError2(x)
509 _unstr = _MODS.streprs.unstr
510 t = _unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
511 raise _Error(s, txt=t, cause=x)
512 try:
513 n = f.write(NN(b, t, e))
514 except UnicodeEncodeError: # XXX only Windows
515 t = t.replace('\u2032', _QUOTE1_).replace('\u2033', _QUOTE2_)
516 n = f.write(NN(b, t, e))
517 if fl: # PYCHOK no cover
518 f.flush()
519 return n
522def _print7(nl=0, nt=0, prec=6, prefix=NN, sep=_SPACE_, file=_sys.stdout,
523 end=_NL_, flush=False, **kwds):
524 '''(INTERNAL) Unravel the C{printf} and remaining keyword arguments.
525 '''
526 if nl > 0:
527 prefix = NN(_NL_ * nl, prefix)
528 if nt > 0:
529 end = NN(end, _NL_ * nt)
530 return prefix, end, file, flush, prec, sep, kwds
533def _PYGEODESY_ENV(which):
534 '''(INTERNAL) Return an ENV C{str} C{PYGEODESY_...}.
535 '''
536 return _UNDER_(_pygeodesy_, typename(which, which)).upper()
539def _Pythonarchine(sep=NN): # in .lazily, .test/bases versions
540 '''(INTERNAL) Get PyPy and Python versions, bits and machine as C{3- or 4-list} or C{str}.
541 '''
542 l3 = _MODS.Pythonarchine
543 return sep.join(l3) if sep else l3 # 3- or 4-list
546def _secs2str(secs): # in .geoids, ../test/bases, .pyrdnap
547 '''Convert a time in C{secs} to C{str}.
548 '''
549 if secs < _100_0:
550 unit = len(_SIsecs) - 1
551 while 0 < secs < 1 and unit > 0:
552 secs *= 1e3 # _1000_0
553 unit -= 1
554 t = '%.3f %s' % (secs, _SIsecs[unit])
555 else:
556 m, s = divmod(secs, 60)
557 if m < 60:
558 t = '%d:%06.3f' % (int(m), s)
559 else:
560 h, m = divmod(int(m), 60)
561 t = '%d:%02d:%06.3f' % (h, m, s)
562 return t
565def _sizeof(obj, deep=True):
566 '''(INTERNAL) Recursively size an C{obj}ect.
568 @kwarg deep: If C{True}, include the size of all
569 C{.__dict__.values()} (C{bool}).
571 @return: The C{obj} size in bytes (C{int}), ignoring
572 class attributes and counting instances only
573 once or C{None}.
575 @note: With C{PyPy}, the returned size is always C{None}.
576 '''
577 try:
578 _zB = _sys.getsizeof
579 _zD = _zB(None) # some default
580 except TypeError: # PyPy3.10
581 return None
583 b = _MODS.basics
584 _isiterablen = b.isiterablen
585 _Str_Bytes = b._Strs + b._Bytes # + (range, map)
587 def _zR(s, iterable):
588 z, _s = 0, s.add
589 for o in iterable:
590 i = id(o)
591 if i not in s:
592 _s(i)
593 z += _zB(o, _zD)
594 if isinstance(o, dict):
595 z += _zR(s, o.keys())
596 z += _zR(s, o.values())
597 elif _isiterablen(o) and not \
598 isinstance(o, _Str_Bytes):
599 z += _zR(s, o)
600 elif deep:
601 try: # size instance' attr values only
602 z += _zR(s, o.__dict__.values())
603 except AttributeError: # None, int, etc.
604 pass
605 return z
607 return _zR(set(), (obj,))
610def _sysctl_uint(name):
611 '''(INTERNAL) Get an C{unsigned int sysctl} item by name, I{ONLY on macOS!}
612 '''
613 libc = _load_lib('libc') if _ismacOS() else None
614 if libc: # <https://StackOverflow.com/questions/759892/python-ctypes-and-sysctl>
615 import ctypes as c
616 n = c.c_char_p(_MODS.basics.str2ub(name)) # bytes(name, _utf_8_)
617 u = c.c_uint(0)
618 z = c.c_size_t(c.sizeof(u))
619 r = libc.sysctlbyname(n, c.byref(u), c.byref(z), None, c.c_size_t(0)) # PYCHOK attr
620 else: # not macOS or couldn't find or load 'libc'=
621 r = -2
622 return int(r if r else u.value) # -1 ENOENT error, -2 no libc or not macOS
625def _tailof(name):
626 '''(INTERNAL) Get the base name of qualified C{name} or the C{name}.
627 '''
628 i = name.rfind(_DOT_) + 1
629 return name[i:] if i > 0 else name
632def _under(name): # PYCHOK in .datums, .auxilats, .geodesicw, .ups, .utm, .utmupsBase, ...
633 '''(INTERNAL) Prefix C{name} with an I{underscore}.
634 '''
635 return name if name.startswith(_UNDER_) else NN(_UNDER_, name)
638def _usage(file_py, *args, **opts_help): # in .etm, .geodesici # PYCHOK no cover
639 '''(INTERNAL) Build "usage: python -m ..." cmd line for module B{C{file_py}}.
640 '''
641 if opts_help:
643 def _help(alts=(), help=NN, **unused):
644 if alts and help:
645 h = NN(help, _SPACE_).lstrip(_DASH_)
646 for a in alts:
647 if a.startswith(h):
648 return NN(_DASH_, a),
650 def _opts(opts=NN, alts=(), **unused):
651 # opts='T--v-C-R meter-c|i|n|o'
652 d, fmt = NN, _MODS.streprs.Fmt.SQUARE
653 for o in (opts + _BAR_(*alts)).split(_DASH_):
654 if o:
655 yield fmt(NN(d, _DASH_, o.replace(_BAR_, ' | -')))
656 d = NN
657 else:
658 d = _DASH_
660 args = _help(**opts_help) or (tuple(_opts(**opts_help)) + args)
662 u = _COLON_(typename(_usage)[1:], NN)
663 return _SPACE_(u, *_usage_argv(file_py, *args))
666def _usage_argv(argv0, *args):
667 '''(INTERNAL) Return 3-tuple C{(python, '-m', module, *args)}.
668 '''
669 o = _MODS.os
670 p = o.path
671 m = p.dirname(argv0).replace(o.getcwd(), _ELLIPSIS_) \
672 .replace(o.sep, _DOT_).strip()
673 b, x = p.splitext(p.basename(argv0))
674 if x == '.py' and b != _DMAIN_:
675 m = _DOT_(m or _pygeodesy_, b)
676 p = NN(_python_, _MODS.sys_version_info2[0])
677 return (p, '-m', _enquote(m)) + args
680def _version2(version, n=2):
681 '''(INTERNAL) Split C{B{version} str} into a C{1-, 2- or 3-tuple} of C{int}s.
682 '''
683 t = _version_ints(version.split(_DOT_, 2))
684 if len(t) < n:
685 t += (0,) * n
686 return t[:n]
689def _version_info(package): # in .basics, .karney._kWrapped.Math
690 '''(INTERNAL) Get the C{package.__version_info__} as a 2- or
691 3-tuple C{(major, minor, revision)} if C{int}s.
692 '''
693 try:
694 return _version_ints(package.__version_info__)
695 except AttributeError:
696 return _version2(package.__version__.strip(), n=3)
699def _version_ints(vs):
700 # helper for _version2 and _version_info above
702 def _ints(vs):
703 for v in vs:
704 try:
705 yield int(v.strip())
706 except (TypeError, ValueError):
707 pass
709 return tuple(_ints(vs))
712def _versions(sep=_SPACE_, **prefix_version): # in pyaxqg, pybelbg, pyrdnap
713 '''(INTERNAL) Get pygeodesy, PyPy and Python versions, bits, machine and OS
714 as C{8- or 10-list} or C{str}, with optional prefix name and version.
715 '''
716 vl = [_pygeodesy_, _MODS.version] + _Pythonarchine() + _osversion2()
717 if prefix_version:
718 p, v = _MODS.errors._xkwds_item2(prefix_version)
719 vl = [p, _DOT_(*_version2(v, n=3))] + vl
720 return sep.join(vl) if sep else vl
723__all__ = tuple(map(typename, (machine, print_, printf, typename)))
724__version__ = '26.06.18'
726if __name__ == _DMAIN_:
728 def _main():
729 from pygeodesy import _isfrozen, isLazy
731 print_(*(_versions(sep=NN) + ['_isfrozen', _isfrozen,
732 'isLazy', isLazy]))
734 _main()
736# % python3 -m pygeodesy.internals
737# pygeodesy 25.8.18 Python 3.13.5 64bit arm64 macOS 15.6 _isfrozen False isLazy 1
739# **) MIT License
740#
741# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.
742#
743# Permission is hereby granted, free of charge, to any person obtaining a
744# copy of this software and associated documentation files (the "Software"),
745# to deal in the Software without restriction, including without limitation
746# the rights to use, copy, modify, merge, publish, distribute, sublicense,
747# and/or sell copies of the Software, and to permit persons to whom the
748# Software is furnished to do so, subject to the following conditions:
749#
750# The above copyright notice and this permission notice shall be included
751# in all copies or substantial portions of the Software.
752#
753# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
754# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
755# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
756# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
757# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
758# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
759# OTHER DEALINGS IN THE SOFTWARE.