Coverage for pygeodesy/internals.py: 93%

271 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-09 11:05 -0400

1 

2# -*- coding: utf-8 -*- 

3 

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_ 

11from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used! 

12# from pygeodesy.streprs import anstr, pairs, unstr # _MODS 

13 

14# import os # _MODS 

15# import os.path # _MODS 

16# import sys as _sys # from .interns 

17 

18_0_0 = 0.0 # PYCHOK in .basics, .constants 

19_100_0 = 100.0 # in .constants 

20_arm64_ = 'arm64' 

21_iOS_ = 'iOS' 

22_macOS_ = 'macOS' 

23_SIsecs = 'fs', 'ps', 'ns', 'us', 'ms', 'sec' # reversed 

24_Windows_ = 'Windows' 

25 

26 

27def _DUNDER_nameof(inst, *dflt): 

28 '''(INTERNAL) Get the DUNDER C{.__name__} attr. 

29 ''' 

30 try: 

31 return inst.__name__ 

32 except AttributeError: 

33 pass 

34 return dflt[0] if dflt else inst.__class__.__name__ 

35 

36 

37def _DUNDER_nameof_(*names__): # in .errors._IsnotError 

38 '''(INTERNAL) Yield the _DUNDER_nameof or name. 

39 ''' 

40 return map(_DUNDER_nameof, names__, names__) 

41 

42 

43def _Property_RO(method): 

44 '''(INTERNAL) Can't import L{props.Property_RO}, I{recursively}. 

45 ''' 

46 name = _DUNDER_nameof(method) 

47 

48 def _del(inst, *unused): # PYCHOK no cover 

49 inst.__dict__.pop(name, None) 

50 

51 def _get(inst, *unused): # PYCHOK 2 vs 3 args 

52 try: # to get the cached value immediately 

53 v = inst.__dict__[name] 

54 except (AttributeError, KeyError): 

55 # cache the value in the instance' __dict__ 

56 inst.__dict__[name] = v = method(inst) 

57 return v 

58 

59 def _set(inst, val): # PYCHOK no cover 

60 setattr(inst, name, val) # force error 

61 

62 return property(_get, _set, _del) 

63 

64 

65class _MODS_Base(object): 

66 '''(INTERNAL) Base-class for C{lazily._ALL_MODS}. 

67 ''' 

68 def __delattr__(self, attr): # PYCHOK no cover 

69 self.__dict__.pop(attr, None) 

70 

71 def __setattr__(self, attr, value): # PYCHOK no cover 

72 e = _MODS.errors 

73 n = _DOT_(self.name, attr) 

74 t = _EQUALSPACED_(n, repr(value)) 

75 raise e._AttributeError(_immutable_, txt=t) 

76 

77 @_Property_RO 

78 def basics(self): 

79 '''Get module C{pygeodesy.basics}, I{once}. 

80 ''' 

81 from pygeodesy import basics as b # DON'T _lazy_import2 

82 return b 

83 

84 @_Property_RO 

85 def bits_machine2(self): 

86 '''Get platform 2-list C{[bits, machine]}, I{once}. 

87 ''' 

88 import platform as p 

89 m = p.machine() # ARM64, arm64, x86_64, iPhone13,2, etc. 

90 m = m.replace(_COMMA_, _UNDER_) 

91 if m.lower() == 'x86_64': # PYCHOK on Intel or Rosetta2 ... 

92 v = p.mac_ver()[0] # ... and only on macOS ... 

93 if v and _version2(v) > (10, 15): # ... 11+ aka 10.16 

94 # <https://Developer.Apple.com/forums/thread/659846> 

95 # _sysctl_uint('hw.optional.arm64') and \ 

96 if _sysctl_uint('sysctl.proc_translated'): 

97 m = _UNDER_(_arm64_, m) # Apple Si emulating Intel x86-64 

98 return [p.architecture()[0], # bits 

99 m] # arm64, arm64_x86_64, x86_64, etc. 

100 

101 @_Property_RO 

102 def ctypes3(self): 

103 '''Get C{ctypes.CDLL}, C{find_library} and C{dlopen}, I{once}. 

104 ''' 

105 import ctypes as c 

106 from ctypes.util import find_library as f 

107 

108 def dlopen(name): # on macOS only 

109 return c._dlopen(name, c.DEFAULT_MODE) 

110 

111 return c.CDLL, f, (dlopen if _ismacOS() else None) 

112 

113 @_Property_RO 

114 def errors(self): 

115 '''Get module C{pygeodesy.errors}, I{once}. 

116 ''' 

117 from pygeodesy import errors as e # DON'T _lazy_import2 

118 return e 

119 

120 @_Property_RO 

121 def inspect(self): # in .basics 

122 '''Get module C{inspect}, I{once}. 

123 ''' 

124 import inspect as i 

125 return i 

126 

127 def ios_ver(self): 

128 '''Mimick C{platform.xxx_ver} for C{iOS}. 

129 ''' 

130 try: # Pythonista only 

131 from platform import iOS_ver 

132 t = iOS_ver() 

133 except (AttributeError, ImportError): 

134 t = NN, (NN, NN, NN), NN 

135 return t 

136 

137 @_Property_RO 

138 def name(self): 

139 '''Get this name (C{str}). 

140 ''' 

141 return _DUNDER_nameof(self.__class__) 

142 

143 @_Property_RO 

144 def nix2(self): # PYCHOK no cover 

145 '''Get Linux 2-tuple C{(distro, version)}, I{once}. 

146 ''' 

147 from platform import uname 

148 v, n = NN, uname()[0] # [0] == .system 

149 if n.lower() == 'linux': 

150 try: # use distro only on Linux, not macOS, etc. 

151 import distro # <https://PyPI.org/project/distro> 

152 _a = _MODS.streprs.anstr 

153 v = _a(distro.version()) # first 

154 n = _a(distro.id()) # .name()? 

155 except (AttributeError, ImportError): 

156 pass # v = str(_0_0) 

157 n = n.capitalize() 

158 return n, v 

159 

160 def nix_ver(self): # PYCHOK no cover 

161 '''Mimick C{platform.xxx_ver} for C{*nix}. 

162 ''' 

163 _, v = _MODS.nix2 

164 t = _version2(v, n=3) if v else (NN, NN, NN) 

165 return v, t, machine() 

166 

167 @_Property_RO 

168 def os(self): 

169 '''Get module C{os}, I{once}. 

170 ''' 

171 import os as o 

172 import os.path 

173 return o 

174 

175 @_Property_RO 

176 def osversion2(self): 

177 '''Get 2-list C{[OS, release]}, I{once}. 

178 ''' 

179 import platform as p 

180 _Nix, _ = _MODS.nix2 

181 # - mac_ver() returns ('10.12.5', ..., 'x86_64') on 

182 # macOS and ('10.3.3', ..., 'iPad4,2') on iOS 

183 # - win32_ver is ('XP', ..., 'SP3', ...) on Windows XP SP3 

184 # - platform() returns 'Darwin-16.6.0-x86_64-i386-64bit' 

185 # on macOS and 'Darwin-16.6.0-iPad4,2-64bit' on iOS 

186 # - sys.platform is 'darwin' on macOS, 'ios' on iOS, 

187 # 'win32' on Windows and 'cygwin' on Windows/Gygwin 

188 # - distro.id() and .name() return 'Darwin' on macOS 

189 for n, v in ((_iOS_, _MODS.ios_ver), 

190 (_macOS_, p.mac_ver), 

191 (_Windows_, p.win32_ver), 

192 (_Nix, _MODS.nix_ver), 

193 ('Java', p.java_ver), 

194 ('uname', p.uname)): 

195 v = v()[0] 

196 if v and n: 

197 break 

198 else: 

199 n = v = NN # XXX AssertionError? 

200 return [n, v] 

201 

202 @_Property_RO 

203 def _Popen_kwds2(self): 

204 '''(INTERNAL) Get C{subprocess.Popen} and C{-kwds}. 

205 ''' 

206 import subprocess as s 

207 kwds = dict(creationflags=0, # executable=sys.executable, shell=True, 

208 stdin=s.PIPE, stdout=s.PIPE, stderr=s.STDOUT) 

209 if _MODS.sys_version_info2 > (3, 6): 

210 kwds.update(text=True) 

211 return s.Popen, kwds 

212 

213 @_Property_RO 

214 def Pythonarchine(self): 

215 '''Get 3- or 4-list C{[PyPy, Python, bits, machine]}, I{once}. 

216 ''' 

217 v = _sys.version 

218 l3 = [_Python_(v)] + _MODS.bits_machine2 

219 pypy = _PyPy__(v) 

220 if pypy: # PYCHOK no cover 

221 l3.insert(0, pypy) 

222 return l3 

223 

224 @_Property_RO 

225 def streprs(self): 

226 '''Get module C{pygeodesy.streprs}, I{once}. 

227 ''' 

228 from pygeodesy import streprs as s # DON'T _lazy_import2 

229 return s 

230 

231 @_Property_RO 

232 def sys_version_info2(self): 

233 '''Get C{sys.version_inf0[:2], I{once}. 

234 ''' 

235 return _sys.version_info[:2] 

236 

237 @_Property_RO 

238 def version(self): 

239 '''Get pygeodesy version, I{once}. 

240 ''' 

241 from pygeodesy import version as v 

242 return v 

243 

244_MODS = _MODS_Base() # PYCHOK overwritten by .lazily 

245 

246 

247def _caller3(up, base=True): # in .lazily, .named 

248 '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)} 

249 for the caller B{C{up}} frames back in the Python call stack. 

250 

251 @kwarg base: Use C{B{base}=False} for the fully-qualified file 

252 name, otherwise the base (module) name (C{bool}). 

253 ''' 

254 f = None 

255 _b = _MODS.os.path.basename if base else _passarg 

256 try: 

257 f = _sys._getframe(up + 1) # == inspect.stack()[up + 1][0] 

258 t = _MODS.inspect.getframeinfo(f) 

259 t = t.function, _b(t.filename), t.lineno 

260# or ... 

261 # f = _sys._getframe(up + 1) 

262 # c = f.f_code 

263 # t = (c.co_name, # caller name 

264 # _b(c.co_filename), # file name .py 

265 # f.f_lineno) # line number 

266# or ... 

267 # t = _MODS.inspect.stack()[up + 1] # (frame, filename, lineno, function, ...) 

268 # t = t[3], _b(t[1]), t[2] 

269 except (AttributeError, IndexError, ValueError): 

270 # sys._getframe(1) ... 'importlib._bootstrap' line 1032, 

271 # may throw a ValueError('call stack not deep enough') 

272 t = NN, NN, 0 

273 finally: 

274 del f # break ref cycle 

275 return t 

276 

277 

278def _enquote(strs, quote=_QUOTE2_, white=NN): # in .basics, .solveBase 

279 '''(INTERNAL) Enquote a string containing whitespace or replace 

280 whitespace by C{white} if specified. 

281 ''' 

282 if strs: 

283 t = strs.split() 

284 if len(t) > 1: 

285 strs = white.join(t if white else (quote, strs, quote)) 

286 return strs 

287 

288 

289def _fper(p, q, per=_100_0, prec=1): 

290 '''Format a percentage C{B{p} * B{per} / B{q}} (C{str}). 

291 ''' 

292 return '%.*f%%' % (prec, (float(p) * per / float(q))) 

293 

294 

295_getenv = _MODS.os.getenv # PYCHOK in .lazily, ... 

296 

297 

298def _getPYGEODESY(which, dflt=NN): 

299 '''(INTERNAL) Return an C{PYGEODESY_...} ENV value or C{dflt}. 

300 ''' 

301 return _getenv(_PYGEODESY(which), dflt) 

302 

303 

304def _headof(name): 

305 '''(INTERNAL) Get the head name of qualified C{name} or the C{name}. 

306 ''' 

307 i = name.find(_DOT_) 

308 return name if i < 0 else name[:i] 

309 

310 

311# def _is(a, b): # PYCHOK no cover 

312# '''(INTERNAL) C{a is b}? in C{PyPy} 

313# ''' 

314# return (a == b) if _isPyPy() else (a is b) 

315 

316 

317def _isAppleSi(): # PYCHOK no cover 

318 '''(INTERNAL) Is this C{macOS on Apple Silicon}? (C{bool}) 

319 ''' 

320 return _ismacOS() and machine().startswith(_arm64_) 

321 

322 

323def _is_DUNDER_main(name): 

324 '''(INTERNAL) Return C{bool(name == '__main__')}. 

325 ''' 

326 return name == '__main__' 

327 

328 

329def _isiOS(): # in test/bases 

330 '''(INTERNAL) Is this C{iOS}? (C{bool}) 

331 ''' 

332 return _MODS.osversion2[0] is _iOS_ 

333 

334 

335def _ismacOS(): # in test/bases 

336 '''(INTERNAL) Is this C{macOS}? (C{bool}) 

337 ''' 

338 return _sys.platform[:6] == 'darwin' and \ 

339 _MODS.osversion2[0] is _macOS_ # and _MODS.os.name == 'posix' 

340 

341 

342def _isNix(): # in test/bases 

343 '''(INTERNAL) Is this a C{Linux} distro? (C{str} or L{NN}) 

344 ''' 

345 return _MODS.nix2[0] 

346 

347 

348def _isPyChOK(): # PYCHOK no cover 

349 '''(INTERNAL) Is C{PyChecker} running? (C{bool}) 

350 ''' 

351 # .../pychecker/checker.py --limit 0 --stdlib pygeodesy/<mod>/<name>.py 

352 return _sys.argv[0].endswith('/pychecker/checker.py') or \ 

353 bool(_getPYGEODESY('PYCHOK')) 

354 

355 

356def _isPyPy(): # in test/bases 

357 '''(INTERNAL) Is this C{PyPy}? (C{bool}) 

358 ''' 

359 # platform.python_implementation() == 'PyPy' 

360 return _MODS.Pythonarchine[0].startswith(_PyPy__) 

361 

362 

363def _isWindows(): # in test/bases 

364 '''(INTERNAL) Is this C{Windows}? (C{bool}) 

365 ''' 

366 return _sys.platform[:3] == 'win' and \ 

367 _MODS.osversion2[0] is _Windows_ 

368 

369 

370def _load_lib(name): 

371 '''(INTERNAL) Load a C{dylib}, B{C{name}} must startwith('lib'). 

372 ''' 

373 CDLL, find_lib, dlopen = _MODS.ctypes3 

374 ns = find_lib(name), name 

375 if dlopen: 

376 # macOS 11+ (aka 10.16) no longer provides direct loading of 

377 # system libraries. As a result, C{ctypes.util.find_library} 

378 # will not find any library, unless previously installed by a 

379 # low-level dlopen(name) call (with the library base C{name}). 

380 ns += (_DOT_(name, 'dylib'), 

381 _DOT_(name, 'framework'), _MODS.os.path.join( 

382 _DOT_(name, 'framework'), name)) 

383 else: # not macOS 

384 dlopen = _passarg # no-op 

385 

386 for n in ns: 

387 try: 

388 if n and dlopen(n): # pre-load handle 

389 lib = CDLL(n) # == ctypes.cdll.LoadLibrary(n) 

390 if lib._name: # has a qualified name 

391 return lib 

392 except (AttributeError, OSError): 

393 pass 

394 

395 return None # raise OSError 

396 

397 

398def machine(): 

399 '''Return standard C{platform.machine}, but distinguishing Intel I{native} 

400 from Intel I{emulation} on Apple Silicon (on macOS only). 

401 

402 @return: Machine C{'arm64'} for Apple Silicon I{native}, C{'x86_64'} 

403 for Intel I{native}, C{"arm64_x86_64"} for Intel I{emulation}, 

404 etc. (C{str} with C{comma}s replaced by C{underscore}s). 

405 ''' 

406 return _MODS.bits_machine2[1] 

407 

408 

409def _name_version(pkg): 

410 '''(INTERNAL) Return C{pskg.__name__ + ' ' + .__version__}. 

411 ''' 

412 return _SPACE_(pkg.__name__, pkg.__version__) 

413 

414 

415def _osversion2(sep=NN): # in .lazily, test/bases.versions 

416 '''(INTERNAL) Get the O/S name and release as C{2-list} or C{str}. 

417 ''' 

418 l2 = _MODS.osversion2 

419 return sep.join(l2) if sep else l2 # 2-list() 

420 

421 

422def _passarg(arg): 

423 '''(INTERNAL) Helper, no-op. 

424 ''' 

425 return arg 

426 

427 

428def _passargs(*args): 

429 '''(INTERNAL) Helper, no-op. 

430 ''' 

431 return args 

432 

433 

434def _plural(noun, n, nn=NN): 

435 '''(INTERNAL) Return C{noun}['s'] or C{NN}. 

436 ''' 

437 return NN(noun, _s_) if n > 1 else (noun if n else nn) 

438 

439 

440def _popen2(cmd, stdin=None): # in .mgrs, .solveBase, .testMgrs 

441 '''(INTERNAL) Invoke C{B{cmd} tuple} and return 2-tuple C{(std, status)} 

442 with all C{stdout/-err} output, I{stripped} and C{int} exit status. 

443 ''' 

444 _Popen, kwds = _MODS._Popen_kwds2 

445 p = _Popen(cmd, **kwds) # PYCHOK kwArgs 

446 r = p.communicate(stdin)[0] # stdout + NL + stderr 

447 return _MODS.basics.ub2str(r).strip(), p.returncode 

448 

449 

450def print_(*args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): # PYCHOK no cover 

451 '''Python 3+ C{print}-like formatting and printing. 

452 

453 @arg args: Values to be converted to C{str} and joined by B{C{sep}}, 

454 all positional. 

455 

456 @see: Function L{printf} for further details. 

457 ''' 

458 return printf(NN, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds) 

459 

460 

461def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): 

462 '''C{Printf-style} and Python 3+ C{print}-like formatting and printing. 

463 

464 @arg fmt: U{Printf-style<https://Docs.Python.org/3/library/stdtypes.html# 

465 printf-style-string-formatting>} format specification (C{str}). 

466 @arg args: Arguments to be formatted (any C{type}, all positional). 

467 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments 

468 C{B{nl}=0} for the number of leading blank lines (C{int}), C{B{nt}=0} 

469 the number of trailing blank lines (C{int}), C{B{prefix}=NN} to be 

470 inserted before the formatted text (C{str}) and Python 3+ C{print} 

471 keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and C{B{flush}}. 

472 Any remaining C{B{kwds}} are C{printf-style} name-value pairs to be 

473 formatted, I{iff no B{C{args}} are present} using C{B{prec}=6} for 

474 the number of decimal digits (C{int}). 

475 

476 @return: Number of bytes written. 

477 ''' 

478 b, e, f, fl, p, s, kwds = _print7(**nl_nt_prec_prefix__end_file_flush_sep__kwds) 

479 try: 

480 if args: 

481 t = (fmt % args) if fmt else s.join(map(str, args)) 

482 elif kwds: 

483 t = (fmt % kwds) if fmt else s.join( 

484 _MODS.streprs.pairs(kwds, prec=p)) 

485 else: 

486 t = fmt 

487 except Exception as x: 

488 _E, s = _MODS.errors._xError2(x) 

489 unstr = _MODS.streprs.unstr 

490 t = unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds) 

491 raise _E(s, txt=t, cause=x) 

492 try: 

493 n = f.write(NN(b, t, e)) 

494 except UnicodeEncodeError: # XXX only Windows 

495 t = t.replace('\u2032', _QUOTE1_).replace('\u2033', _QUOTE2_) 

496 n = f.write(NN(b, t, e)) 

497 if fl: # PYCHOK no cover 

498 f.flush() 

499 return n 

500 

501 

502def _print7(nl=0, nt=0, prec=6, prefix=NN, sep=_SPACE_, file=_sys.stdout, 

503 end=_NL_, flush=False, **kwds): 

504 '''(INTERNAL) Unravel the C{printf} and remaining keyword arguments. 

505 ''' 

506 if nl > 0: 

507 prefix = NN(_NL_ * nl, prefix) 

508 if nt > 0: 

509 end = NN(end, _NL_ * nt) 

510 return prefix, end, file, flush, prec, sep, kwds 

511 

512 

513def _PYGEODESY(which, i=0): 

514 '''(INTERNAL) Return an ENV C{str} C{PYGEODESY_...}. 

515 ''' 

516 try: 

517 w = which.__name__.lstrip(_UNDER_)[i:] 

518 except AttributeError: 

519 w = which 

520 return _UNDER_(_pygeodesy_, w).upper() 

521 

522 

523def _Pythonarchine(sep=NN): # in .lazily, test/bases versions 

524 '''(INTERNAL) Get PyPy and Python versions, bits and machine as C{3- or 4-list} or C{str}. 

525 ''' 

526 l3 = _MODS.Pythonarchine 

527 return sep.join(l3) if sep else l3 # 3- or 4-list 

528 

529 

530def _secs2str(secs): # in .geoids, ../test/bases 

531 '''Convert a time in C{secs} to C{str}. 

532 ''' 

533 if secs < _100_0: 

534 unit = len(_SIsecs) - 1 

535 while 0 < secs < 1 and unit > 0: 

536 secs *= 1e3 # _1000_0 

537 unit -= 1 

538 t = '%.3f %s' % (secs, _SIsecs[unit]) 

539 else: 

540 m, s = divmod(secs, 60) 

541 if m < 60: 

542 t = '%d:%06.3f' % (int(m), s) 

543 else: 

544 h, m = divmod(int(m), 60) 

545 t = '%d:%02d:%06.3f' % (h, m, s) 

546 return t 

547 

548 

549def _sizeof(obj, deep=True): 

550 '''(INTERNAL) Recursively size an C{obj}ect. 

551 

552 @kwarg deep: If C{True}, include the size of all 

553 C{.__dict__.values()} (C{bool}). 

554 

555 @return: The C{obj} size in bytes (C{int}), ignoring 

556 class attributes and counting instances only 

557 once or C{None}. 

558 

559 @note: With C{PyPy}, the returned size is always C{None}. 

560 ''' 

561 try: 

562 _zB = _sys.getsizeof 

563 _zD = _zB(None) # some default 

564 except TypeError: # PyPy3.10 

565 return None 

566 

567 b = _MODS.basics 

568 _isiterablen = b.isiterablen 

569 _Str_Bytes = b._Strs + b._Bytes # + (range, map) 

570 

571 def _zR(s, iterable): 

572 z, _s = 0, s.add 

573 for o in iterable: 

574 i = id(o) 

575 if i not in s: 

576 _s(i) 

577 z += _zB(o, _zD) 

578 if isinstance(o, dict): 

579 z += _zR(s, o.keys()) 

580 z += _zR(s, o.values()) 

581 elif _isiterablen(o) and not \ 

582 isinstance(o, _Str_Bytes): 

583 z += _zR(s, o) 

584 elif deep: 

585 try: # size instance' attr values only 

586 z += _zR(s, o.__dict__.values()) 

587 except AttributeError: # None, int, etc. 

588 pass 

589 return z 

590 

591 return _zR(set(), (obj,)) 

592 

593 

594def _sysctl_uint(name): 

595 '''(INTERNAL) Get an C{unsigned int sysctl} item by name, I{ONLY on macOS!} 

596 ''' 

597 libc = _load_lib('libc') if _ismacOS() else None 

598 if libc: # <https://StackOverflow.com/questions/759892/python-ctypes-and-sysctl> 

599 import ctypes as c 

600 n = c.c_char_p(_MODS.basics.str2ub(name)) # bytes(name, _utf_8_) 

601 u = c.c_uint(0) 

602 z = c.c_size_t(c.sizeof(u)) 

603 r = libc.sysctlbyname(n, c.byref(u), c.byref(z), None, c.c_size_t(0)) # PYCHOK attr 

604 else: # not macOS or couldn't find or load 'libc'= 

605 r = -2 

606 return int(r if r else u.value) # -1 ENOENT error, -2 no libc or not macOS 

607 

608 

609def _tailof(name): 

610 '''(INTERNAL) Get the base name of qualified C{name} or the C{name}. 

611 ''' 

612 i = name.rfind(_DOT_) + 1 

613 return name[i:] if i > 0 else name 

614 

615 

616def _under(name): # PYCHOK in .datums, .auxilats, .ups, .utm, .utmupsBase, ... 

617 '''(INTERNAL) Prefix C{name} with an I{underscore}. 

618 ''' 

619 return name if name.startswith(_UNDER_) else NN(_UNDER_, name) 

620 

621 

622def _usage(file_py, *args, **opts_help): # in .etm, .geodesici # PYCHOK no cover 

623 '''(INTERNAL) Build "usage: python -m ..." cmd line for module B{C{file_py}}. 

624 ''' 

625 if opts_help: 

626 

627 def _help(alts=(), help=NN, **unused): 

628 if alts and help: 

629 h = NN(help, _SPACE_).lstrip(_DASH_) 

630 for a in alts: 

631 if a.startswith(h): 

632 return NN(_DASH_, a), 

633 

634 def _opts(opts=NN, alts=(), **unused): 

635 # opts='T--v-C-R meter-c|i|n|o' 

636 d, fmt = NN, _MODS.streprs.Fmt.SQUARE 

637 for o in (opts + _BAR_(*alts)).split(_DASH_): 

638 if o: 

639 yield fmt(NN(d, _DASH_, o.replace(_BAR_, ' | -'))) 

640 d = NN 

641 else: 

642 d = _DASH_ 

643 

644 args = _help(**opts_help) or (tuple(_opts(**opts_help)) + args) 

645 

646 u = _COLON_(_DUNDER_nameof(_usage)[1:], NN) 

647 return _SPACE_(u, *_usage_argv(file_py, *args)) 

648 

649 

650def _usage_argv(argv0, *args): 

651 '''(INTERNAL) Return 3-tuple C{(python, '-m', module, *args)}. 

652 ''' 

653 o = _MODS.os 

654 m = o.path.dirname(argv0) 

655 m = m.replace(o.getcwd(), _ELLIPSIS_) \ 

656 .replace(o.sep, _DOT_).strip() 

657 b = o.path.basename(argv0) 

658 b, x = o.path.splitext(b) 

659 if x == '.py' and not _is_DUNDER_main(b): 

660 m = _DOT_(m or _pygeodesy_, b) 

661 p = NN(_python_, _MODS.sys_version_info2[0]) 

662 return (p, '-m', _enquote(m)) + args 

663 

664 

665def _version2(version, n=2): 

666 '''(INTERNAL) Split C{B{version} str} into a C{1-, 2- or 3-tuple} of C{int}s. 

667 ''' 

668 t = _version_ints(version.split(_DOT_, 2)) 

669 if len(t) < n: 

670 t += (0,) * n 

671 return t[:n] 

672 

673 

674def _version_info(package): # in .basics, .karney._kWrapped.Math 

675 '''(INTERNAL) Get the C{package.__version_info__} as a 2- or 

676 3-tuple C{(major, minor, revision)} if C{int}s. 

677 ''' 

678 try: 

679 return _version_ints(package.__version_info__) 

680 except AttributeError: 

681 return _version2(package.__version__.strip(), n=3) 

682 

683 

684def _version_ints(vs): 

685 # helper for _version2 and _version_info above 

686 

687 def _ints(vs): 

688 for v in vs: 

689 try: 

690 yield int(v.strip()) 

691 except (TypeError, ValueError): 

692 pass 

693 

694 return tuple(_ints(vs)) 

695 

696 

697def _versions(sep=_SPACE_): 

698 '''(INTERNAL) Get pygeodesy, PyPy and Python versions, bits, machine and OS as C{8- or 9-list} or C{str}. 

699 ''' 

700 l7 = [_pygeodesy_, _MODS.version] + _Pythonarchine() + _osversion2() 

701 return sep.join(l7) if sep else l7 # 5- or 6-list 

702 

703 

704__all__ = tuple(map(_DUNDER_nameof, (machine, print_, printf))) 

705__version__ = '25.04.04' 

706 

707if _is_DUNDER_main(__name__): # PYCHOK no cover 

708 

709 def _main(): 

710 from pygeodesy import _isfrozen, isLazy 

711 

712 print_(*(_versions(sep=NN) + ['_isfrozen', _isfrozen, 

713 'isLazy', isLazy])) 

714 

715 _main() 

716 

717# % python3 -m pygeodesy.internals 

718# pygeodesy 25.4.4 Python 3.13.2 64bit arm64 macOS 15.4 _isfrozen False isLazy 1 

719 

720# **) MIT License 

721# 

722# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

723# 

724# Permission is hereby granted, free of charge, to any person obtaining a 

725# copy of this software and associated documentation files (the "Software"), 

726# to deal in the Software without restriction, including without limitation 

727# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

728# and/or sell copies of the Software, and to permit persons to whom the 

729# Software is furnished to do so, subject to the following conditions: 

730# 

731# The above copyright notice and this permission notice shall be included 

732# in all copies or substantial portions of the Software. 

733# 

734# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

735# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

736# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

737# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

738# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

739# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

740# OTHER DEALINGS IN THE SOFTWARE.