Coverage for pygeodesy/internals.py: 91%

1

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

3

4 u'''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, _xError2 # _MODS

8 from pygeodesy.interns import _BAR_, _COLON_, _DASH_, _DMAIN_, _DOT_, _ELLIPSIS_, _NL_, NN, \

9 _NLATvar_, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, _QUOTE2_, \

10 _s_, _sys, _SPACE_, _UNDER_

11 from 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

27 def typename(obj, *dflt):

28 '''Get the C{obj.__name__}, the C{dflt} or its outer C{type.__name__} or C{NN} (C{str}).

29 '''

30 try:

31 return obj.__name__

32 except (AttributeError, ImportError): # LazyImportError

33 pass

34 return dflt[0] if dflt else typename(type(obj), NN)

35

36

37 def _Property_RO(method):

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

39 '''

40 name = typename(method)

41

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

43 inst.__dict__.pop(name, None)

44

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

46 try: # to get the cached value immediately

47 v = inst.__dict__[name]

48 except (AttributeError, KeyError):

49 # cache the value in the instance' __dict__

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

51 return v

52

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

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

55

56 return property(_get, _set, _del)

57

58

59 class _Enum(object): # in .elliptic, .utily

60 '''(INTERNAL) Enum-like, immutable items.

61 '''

62 # _ImmutableError = None

63

64 def __init__(self, **enums):

65 self.__dict__.update(enums)

66 # for item in enums.items():

67 # setattr(self, *item) # object.__setattr__

68

69 def __str__(self):

70 _unstr = _MODS.streprs.unstr

71 return _unstr(_Enum, **self.__dict__)

72

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

74 # raise _ImmutableError(self, attr) # _del_

75

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

77 # raise _ImmutableError(self, attr, value)

78

79

80 class _MODS_Base(object):

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

82 '''

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

84 self.__dict__.pop(attr, None)

85

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

87 raise _ImmutableError(self, attr, value)

88

89 @_Property_RO

90 def basics(self):

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

92 '''

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

94 return b

95

96 @_Property_RO

97 def bits_machine2(self): # in test/bases.py

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

99 '''

100 import platform as p

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

102 m = m.replace(_COMMA_, _UNDER_)

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

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

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

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

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

108 if _sysctl_uint('sysctl.proc_translated'):

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

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

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

112

113 @_Property_RO

114 def ctypes3(self):

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

116 '''

117 import ctypes as c

118 from ctypes.util import find_library as f

119

120 def dlopen(name): # on macOS only

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

122

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

124

125 @_Property_RO

126 def errors(self):

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

128 '''

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

130 return e

131

132 @_Property_RO

133 def inspect(self): # in .basics

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

135 '''

136 import inspect as i

137 return i

138

139 def ios_ver(self):

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

141 '''

142 try: # Pythonista only

143 from platform import iOS_ver

144 t = iOS_ver()

145 except (AttributeError, ImportError):

146 t = NN, (NN, NN, NN), NN

147 return t

148

149 @_Property_RO

150 def name(self):

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

152 '''

153 return typename(type(self))

154

155 @_Property_RO

156 def nix2(self): # PYCHOK no cover

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

158 '''

159 from platform import uname

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

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

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

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

164 _a = _MODS.streprs.anstr

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

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

167 except (AttributeError, ImportError):

168 pass # v = str(_0_0)

169 n = n.capitalize()

170 return n, v

171

172 def nix_ver(self): # PYCHOK no cover

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

174 '''

175 _, v = _MODS.nix2

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

177 return v, t, machine()

178

179 @_Property_RO

180 def os(self):

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

182 '''

183 import os as o

184 import os.path

185 return o

186

187 @_Property_RO

188 def osversion2(self):

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

190 '''

191 import platform as p

192 _Nix, _ = _MODS.nix2

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

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

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

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

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

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

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

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

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

202 (_macOS_, p.mac_ver),

203 (_Windows_, p.win32_ver),

204 (_Nix, _MODS.nix_ver),

205# removed Py 3.15 ('Java', p.java_ver),

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

207 v = v()[0]

208 if v and n:

209 break

210 else:

211 n = v = NN # XXX AssertionError?

212 return [n, v]

213

214 @_Property_RO

215 def _Popen_kwds2(self):

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

217 '''

218 import subprocess as s

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

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

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

222 kwds.update(text=True)

223 return s.Popen, kwds

224

225 @_Property_RO

226 def Pythonarchine(self):

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

228 '''

229 v = _sys.version

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

231 pypy = _PyPy__(v)

232 if pypy: # PYCHOK no cover

233 l3.insert(0, pypy)

234 return l3

235

236 @_Property_RO

237 def streprs(self):

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

239 '''

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

241 return s

242

243 @_Property_RO

244 def sys_version_info2(self):

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

246 '''

247 return _sys.version_info[:2]

248

249 @_Property_RO

250 def version(self):

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

252 '''

253 from pygeodesy import version as v

254 return v

255

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

257

258

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

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

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

262

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

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

265 '''

266 f = None

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

268 try:

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

270 t = _MODS.inspect.getframeinfo(f)

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

272# or ...

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

274 # c = f.f_code

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

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

277 # f.f_lineno) # line number

278# or ...

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

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

281 except (AttributeError, IndexError, ValueError):

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

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

284 t = NN, NN, 0

285 finally:

286 del f # break ref cycle

287 return t

288

289

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

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

292 whitespace by C{white} if specified.

293 '''

294 if strs:

295 t = strs.split()

296 if len(t) > 1:

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

298 return strs

299

300

301 def _envPYGEODESY(which, dflt=NN):

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

303 '''

304 return _getenv(_PYGEODESY_ENV(which), dflt)

305

306

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

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

309 '''

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

311

312

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

314

315

316 def _headof(name):

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

318 '''

319 i = name.find(_DOT_)

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

321

322

323 def _ImmutableError(*inst_attr_value):

324 '''(INTERNAL) Format an C{_ImmutableError}.

325 '''

326 return _MODS.errors._ImmutableError(*inst_attr_value)

327

328

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

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

331# '''

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

333

334

335 def _isAppleSi(): # PYCHOK no cover

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

337 '''

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

339

340

341 def _isiOS(): # in test/bases

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

343 '''

344 return _MODS.osversion2[0] is _iOS_

345

346

347 def _ismacOS(): # in test/bases

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

349 '''

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

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

352

353

354 def _isNix(): # in test/bases

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

356 '''

357 return _MODS.nix2[0]

358

359

360 def _isPyChOK(): # PYCHOK no cover

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

362 '''

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

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

365 bool(_envPYGEODESY('PYCHOK'))

366

367

368 def _isPyPy(): # in test/bases

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

370 '''

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

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

373

374

375 def _isWindows(): # in test/bases

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

377 '''

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

379 _MODS.osversion2[0] is _Windows_

380

381

382 def _load_lib(name):

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

384 '''

385 CDLL, find_lib, dlopen = _MODS.ctypes3

386 ns = find_lib(name), name

387 if dlopen:

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

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

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

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

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

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

394 _DOT_(name, 'framework'), name))

395 else: # not macOS

396 dlopen = _passarg # no-op

397

398 for n in ns:

399 try:

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

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

402 if lib._name: # has a qualified name

403 return lib

404 except (AttributeError, OSError):

405 pass

406

407 return None # raise OSError

408

409

410 def machine():

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

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

413

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

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

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

417 '''

418 return _MODS.bits_machine2[1]

419

420

421 def _name_version(pkg):

422 '''(INTERNAL) Return C{pkg.__name__ + ' ' + .__version__}.

423 '''

424 return _SPACE_(typename(pkg), pkg.__version__) # _DVERSION_

425

426

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

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

429 '''

430 l2 = _MODS.osversion2

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

432

433

434 def _passarg(arg):

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

436 '''

437 return arg

438

439

440 def _passargs(*args):

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

442 '''

443 return args

444

445

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

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

448 '''

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

450

451

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

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

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

455 '''

456 _Popen, kwds = _MODS._Popen_kwds2

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

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

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

460

461

462 def _pregistry(registry):

463 '''(INTERNAL) Print all items of a C{registry}.

464 '''

465 t = [NN] + registry.toRepr(all=True, asorted=True).split(_NL_)

466 printf(_NLATvar_.join(i.strip(_COMMA_) for i in t))

467

468

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

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

471

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

473 all positional.

474

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

476 '''

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

478

479

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

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

482

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

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

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

486 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments

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

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

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

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

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

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

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

494

495 @return: Number of bytes written.

496 '''

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

498 try:

499 if args:

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

501 elif kwds:

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

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

504 else:

505 t = fmt

506 except Exception as x:

507 _Error, s = _MODS.errors._xError2(x)

508 _unstr = _MODS.streprs.unstr

509 t = _unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)

510 raise _Error(s, txt=t, cause=x)

511 try:

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

513 except UnicodeEncodeError: # XXX only Windows

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

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

516 if fl: # PYCHOK no cover

517 f.flush()

518 return n

519

520

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

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

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

524 '''

525 if nl > 0:

526 prefix = NN(_NL_ * nl, prefix)

527 if nt > 0:

528 end = NN(end, _NL_ * nt)

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

530

531

532 def _PYGEODESY_ENV(which):

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

534 '''

535 return _UNDER_(_pygeodesy_, typename(which, which)).upper()

536

537

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

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

540 '''

541 l3 = _MODS.Pythonarchine

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

543

544

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

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

547 '''

548 if secs < _100_0:

549 unit = len(_SIsecs) - 1

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

551 secs *= 1e3 # _1000_0

552 unit -= 1

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

554 else:

555 m, s = divmod(secs, 60)

556 if m < 60:

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

558 else:

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

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

561 return t

562

563

564 def _sizeof(obj, deep=True):

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

566

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

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

569

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

571 class attributes and counting instances only

572 once or C{None}.

573

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

575 '''

576 try:

577 _zB = _sys.getsizeof

578 _zD = _zB(None) # some default

579 except TypeError: # PyPy3.10

580 return None

581

582 b = _MODS.basics

583 _isiterablen = b.isiterablen

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

585

586 def _zR(s, iterable):

587 z, _s = 0, s.add

588 for o in iterable:

589 i = id(o)

590 if i not in s:

591 _s(i)

592 z += _zB(o, _zD)

593 if isinstance(o, dict):

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

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

596 elif _isiterablen(o) and not \

597 isinstance(o, _Str_Bytes):

598 z += _zR(s, o)

599 elif deep:

600 try: # size instance' attr values only

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

602 except AttributeError: # None, int, etc.

603 pass

604 return z

605

606 return _zR(set(), (obj,))

607

608

609 def _sysctl_uint(name):

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

611 '''

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

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

614 import ctypes as c

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

616 u = c.c_uint(0)

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

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

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

620 r = -2

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

622

623

624 def _tailof(name):

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

626 '''

627 i = name.rfind(_DOT_) + 1

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

629

630

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

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

633 '''

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

635

636

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

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

639 '''

640 if opts_help:

641

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

643 if alts and help:

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

645 for a in alts:

646 if a.startswith(h):

647 return NN(_DASH_, a),

648

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

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

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

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

653 if o:

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

655 d = NN

656 else:

657 d = _DASH_

658

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

660

661 u = _COLON_(typename(_usage)[1:], NN)

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

663

664

665 def _usage_argv(argv0, *args):

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

667 '''

668 o = _MODS.os

669 p = o.path

670 m = p.dirname(argv0).replace(o.getcwd(), _ELLIPSIS_) \

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

672 b, x = p.splitext(p.basename(argv0))

673 if x == '.py' and b != _DMAIN_:

674 m = _DOT_(m or _pygeodesy_, b)

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

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

677

678

679 def _version2(version, n=2):

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

681 '''

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

683 if len(t) < n:

684 t += (0,) * n

685 return t[:n]

686

687

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

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

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

691 '''

692 try:

693 return _version_ints(package.__version_info__)

694 except AttributeError:

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

696

697

698 def _version_ints(vs):

699 # helper for _version2 and _version_info above

700

701 def _ints(vs):

702 for v in vs:

703 try:

704 yield int(v.strip())

705 except (TypeError, ValueError):

706 pass

707

708 return tuple(_ints(vs))

709

710

711 def _versions(sep=_SPACE_):

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

713 '''

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

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

716

717

718 __all__ = tuple(map(typename, (machine, print_, printf, typename)))

719 __version__ = '26.01.13'

720

721 if __name__ == _DMAIN_:

722

723 def _main():

724 from pygeodesy import _isfrozen, isLazy

725

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

727 'isLazy', isLazy]))

728

729 _main()

730

731# % python3 -m pygeodesy.internals

732# pygeodesy 25.8.18 Python 3.13.5 64bit arm64 macOS 15.6 _isfrozen False isLazy 1

733

734# **) MIT License

735#

736# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

737#

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

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

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

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

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

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

744#

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

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

747#

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

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

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

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

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

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

754# OTHER DEALINGS IN THE SOFTWARE.