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, _UnexpectedError, _xError2 # _MODS

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

9 _immutable_, _NL_, _pygeodesy_, _PyPy__, _python_, _QUOTE1_, \

10 _QUOTE2_, _s_, _SPACE_, _sys, _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 _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

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

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

39 '''

40 return map(_DUNDER_nameof, names__, names__)

41

42

43 def _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

65 class _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

247 def _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

278 def _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

289 def _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

298 def _getPYGEODESY(which, dflt=NN):

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

300 '''

301 return _getenv(_PYGEODESY(which), dflt)

302

303

304 def _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

317 def _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

323 def _is_DUNDER_main(name):

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

325 '''

326 return name == '__main__'

327

328

329 def _isiOS(): # in test/bases

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

331 '''

332 return _MODS.osversion2[0] is _iOS_

333

334

335 def _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

342 def _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

348 def _isPyChecker(): # 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')

353

354

355 def _isPyPy(): # in test/bases

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

357 '''

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

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

360

361

362 def _isWindows(): # in test/bases

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

364 '''

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

366 _MODS.osversion2[0] is _Windows_

367

368

369 def _load_lib(name):

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

371 '''

372 CDLL, find_lib, dlopen = _MODS.ctypes3

373 ns = find_lib(name), name

374 if dlopen:

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

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

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

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

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

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

381 _DOT_(name, 'framework'), name))

382 else: # not macOS

383 dlopen = _passarg # no-op

384

385 for n in ns:

386 try:

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

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

389 if lib._name: # has a qualified name

390 return lib

391 except (AttributeError, OSError):

392 pass

393

394 return None # raise OSError

395

396

397 def machine():

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

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

400

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

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

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

404 '''

405 return _MODS.bits_machine2[1]

406

407

408 def _name_version(pkg):

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

410 '''

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

412

413

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

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

416 '''

417 l2 = _MODS.osversion2

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

419

420

421 def _passarg(arg):

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

423 '''

424 return arg

425

426

427 def _passargs(*args):

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

429 '''

430 return args

431

432

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

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

435 '''

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

437

438

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

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

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

442 '''

443 _Popen, kwds = _MODS._Popen_kwds2

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

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

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

447

448

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

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

451

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

453 all positional.

454

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

456 '''

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

458

459

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

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

462

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

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

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

466 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments

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

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

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

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

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

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

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

474

475 @return: Number of bytes written.

476 '''

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

478 try:

479 if args:

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

481 elif kwds:

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

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

484 else:

485 t = fmt

486 except Exception as x:

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

488 unstr = _MODS.streprs.unstr

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

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

491 try:

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

493 except UnicodeEncodeError: # XXX only Windows

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

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

496 if fl: # PYCHOK no cover

497 f.flush()

498 return n

499

500

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

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

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

504 '''

505 if nl > 0:

506 prefix = NN(_NL_ * nl, prefix)

507 if nt > 0:

508 end = NN(end, _NL_ * nt)

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

510

511

512 def _PYGEODESY(which, i=0):

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

514 '''

515 try:

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

517 except AttributeError:

518 w = which

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

520

521

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

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

524 '''

525 l3 = _MODS.Pythonarchine

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

527

528

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

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

531 '''

532 if secs < _100_0:

533 unit = len(_SIsecs) - 1

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

535 secs *= 1e3 # _1000_0

536 unit -= 1

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

538 else:

539 m, s = divmod(secs, 60)

540 if m < 60:

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

542 else:

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

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

545 return t

546

547

548 def _sizeof(obj, deep=True):

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

550

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

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

553

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

555 class attributes and counting instances only

556 once or C{None}.

557

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

559 '''

560 try:

561 _zB = _sys.getsizeof

562 _zD = _zB(None) # some default

563 except TypeError: # PyPy3.10

564 return None

565

566 b = _MODS.basics

567 _isiterablen = b.isiterablen

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

569

570 def _zR(s, iterable):

571 z, _s = 0, s.add

572 for o in iterable:

573 i = id(o)

574 if i not in s:

575 _s(i)

576 z += _zB(o, _zD)

577 if isinstance(o, dict):

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

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

580 elif _isiterablen(o) and not \

581 isinstance(o, _Str_Bytes):

582 z += _zR(s, o)

583 elif deep:

584 try: # size instance' attr values only

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

586 except AttributeError: # None, int, etc.

587 pass

588 return z

589

590 return _zR(set(), (obj,))

591

592

593 def _sysctl_uint(name):

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

595 '''

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

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

598 import ctypes as c

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

600 u = c.c_uint(0)

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

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

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

604 r = -2

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

606

607

608 def _tailof(name):

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

610 '''

611 i = name.rfind(_DOT_) + 1

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

613

614

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

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

617 '''

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

619

620

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

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

623 '''

624 if opts_help:

625

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

627 if alts and help:

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

629 for a in alts:

630 if a.startswith(h):

631 return NN(_DASH_, a),

632

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

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

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

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

637 if o:

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

639 d = NN

640 else:

641 d = _DASH_

642

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

644

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

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

647

648

649 def _usage_argv(argv0, *args):

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

651 '''

652 o = _MODS.os

653 m = o.path.dirname(argv0)

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

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

656 b = o.path.basename(argv0)

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

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

659 m = _DOT_(m or _pygeodesy_, b)

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

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

662

663

664 def _version2(version, n=2):

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

666 '''

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

668 if len(t) < n:

669 t += (0,) * n

670 return t[:n]

671

672

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

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

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

676 '''

677 try:

678 return _version_ints(package.__version_info__)

679 except AttributeError:

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

681

682

683 def _version_ints(vs):

684 # helper for _version2 and _version_info above

685

686 def _ints(vs):

687 for v in vs:

688 try:

689 yield int(v.strip())

690 except (TypeError, ValueError):

691 pass

692

693 return tuple(_ints(vs))

694

695

696 def _versions(sep=_SPACE_):

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

698 '''

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

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

701

702

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

704__version__ = '24.11.06'

705

706 if _is_DUNDER_main(__name__): # PYCHOK no cover

707

708 def _main():

709 from pygeodesy import _isfrozen, isLazy

710

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

712 'isLazy', isLazy]))

713

714 _main()

715

716# % python3 -m pygeodesy.internals

717# pygeodesy 24.11.11 Python 3.13.0 64bit arm64 macOS 14.6.1 _isfrozen False isLazy 1

718

719# **) MIT License

720#

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

722#

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

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

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

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

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

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

729#

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

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

732#

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

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

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

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

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

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

739# OTHER DEALINGS IN THE SOFTWARE.