1# -*- coding: utf-8 -*-

2

3 u'''Mostly INTERNAL functions, except L{machine}, L{print_} and L{printf}.

4'''

5# from pygeodesy.basics import isiterablen # _MODS

6# from pygeodesy.errors import _AttributeError, _error_init, _UnexpectedError, _xError2 # _MODS

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

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

9 _QUOTE2_, _s_, _SPACE_, _sys, _UNDER_, _utf_8_

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

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

12

13 import os as _os # in .lazily, ...

14 import os.path as _os_path

15# import sys as _sys # from .interns

16

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

18_arm64_ = 'arm64'

19_iOS_ = 'iOS'

20_macOS_ = 'macOS'

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

22_Windows_ = 'Windows'

23

24

25 def _dunder_nameof(inst, *dflt):

26 '''(INTERNAL) Get the double_underscore __name__ attr.

27 '''

28 try:

29 return inst.__name__

30 except AttributeError:

31 pass

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

33

34

35 def _dunder_nameof_(*names__): # in .errors._IsnotError

36 '''(INTERNAL) Yield the _dunder_nameof or name.

37 '''

38 return map(_dunder_nameof, names__, names__)

39

40

41 def _Property_RO(method):

42 '''(INTERNAL) Can't I{recursively} import L{props.property_RO}.

43 '''

44 name = _dunder_nameof(method)

45

46 def _del(inst, attr): # PYCHOK no cover

47 delattr(inst, attr) # force error

48

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

50 try: # to get the cached value immediately

51 v = inst.__dict__[name]

52 except (AttributeError, KeyError):

53 # cache the value in the instance' __dict__

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

55 return v

56

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

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

59

60 return property(_get, _set, _del)

61

62

63 class _MODS_Base(object):

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

65 '''

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

67 self.__dict__.pop(attr, None)

68

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

70 m = _MODS.errors

71 t = _EQUALSPACED_(self._DOT_(attr), repr(value))

72 raise m._AttributeError(_immutable_, txt=t)

73

74 @_Property_RO

75 def bits_machine2(self):

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

77 '''

78 import platform as p

79

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

81 m = m.replace(_COMMA_, _UNDER_)

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

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

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

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

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

87 if _sysctl_uint('sysctl.proc_translated'):

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

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

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

91

92 @_Property_RO

93 def ctypes3(self):

94 '''Get 3-tuple C{(ctypes.CDLL, ._dlopen, .util.findlibrary)}, I{once}.

95 '''

96 if _ismacOS():

97 from ctypes import CDLL, DEFAULT_MODE, _dlopen

98

99 def dlopen(name):

100 return _dlopen(name, DEFAULT_MODE)

101 else: # PYCHOK no cover

102 from ctypes import CDLL

103 dlopen = _passarg

104

105 from ctypes.util import find_library

106 return CDLL, dlopen, find_library

107

108 @_Property_RO

109 def ctypes5(self):

110 '''Get 5-tuple C{(ctypes.byref, .c_char_p, .c_size_t, .c_uint, .sizeof)}, I{once}.

111 '''

112 from ctypes import byref, c_char_p, c_size_t, c_uint, sizeof # get_errno

113 return byref, c_char_p, c_size_t, c_uint, sizeof

114

115 def _DOT_(self, name): # PYCHOK no cover

116 return _DOT_(self.name, name)

117

118 @_Property_RO

119 def errors(self):

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

121 '''

122 from pygeodesy import errors # DON'T _lazy_import2

123 return errors

124

125 def ios_ver(self):

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

127 '''

128 try: # Pythonista only

129 from platform import iOS_ver

130 return iOS_ver()

131 except (AttributeError, ImportError):

132 return NN, (NN, NN, NN), NN

133

134 @_Property_RO

135 def libc(self):

136 '''Load C{libc.dll|dylib}, I{once}.

137 '''

138 return _load_lib('libc')

139

140 @_Property_RO

141 def name(self):

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

143 '''

144 return _dunder_nameof(self.__class__)

145

146 @_Property_RO

147 def nix2(self): # PYCHOK no cover

148 '''Get Linux 2-list C{[distro, version]}, I{once}.

149 '''

150 import platform as p

151

152 n, v = p.uname()[0], NN

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

154 try: # use distro only for Linux, not macOS, etc.

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

156 _a = _MODS.streprs.anstr

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

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

159 except (AttributeError, ImportError):

160 pass # v = str(_0_0)

161 n = n.capitalize()

162 return n, v

163

164 def nix_ver(self): # PYCHOK no cover

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

166 '''

167 _, v = _MODS.nix2

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

169 return v, t, machine()

170

171 @_Property_RO

172 def osversion2(self):

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

174 '''

175 import platform as p

176

177 _Nix, _ = _MODS.nix2

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

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

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

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

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

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

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

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

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

187 (_macOS_, p.mac_ver),

188 (_Windows_, p.win32_ver),

189 (_Nix, _MODS.nix_ver),

190 ('Java', p.java_ver),

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

192 v = v()[0]

193 if v and n:

194 break

195 else:

196 n = v = NN # XXX AssertioError?

197 return [n, v]

198

199 @_Property_RO

200 def Pythonarchine(self):

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

202 '''

203 v = _sys.version

204 l3 = [_Python_(v)] + self.bits_machine2

205 pypy = _PyPy__(v)

206 if pypy: # PYCHOK no cover

207 l3.insert(0, pypy)

208 return l3

209

210 @_Property_RO

211 def streprs(self):

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

213 '''

214 from pygeodesy import streprs # DON'T _lazy_import2

215 return streprs

216

217 @_Property_RO

218 def version(self):

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

220 '''

221 from pygeodesy import version

222 return version

223

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

225

226

227 def _caller3(up): # in .lazily, .named

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

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

230 '''

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

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

233 f = _sys._getframe(up + 1)

234 c = f.f_code

235 return (c.co_name, # caller name

236 _os_path.basename(c.co_filename), # file name .py

237 f.f_lineno) # line number

238

239

240 def _dunder_ismain(name):

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

242 '''

243 return name == '__main__'

244

245

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

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

248 whitespace by C{white} if specified.

249 '''

250 if strs:

251 t = strs.split()

252 if len(t) > 1:

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

254 return strs

255

256

257 def _headof(name):

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

259 '''

260 i = name.find(_DOT_)

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

262

263

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

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

266# '''

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

268

269

270 def _isAppleM():

271 '''(INTERNAL) Is this C{Apple Silicon}? (C{bool})

272 '''

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

274

275

276 def _isiOS(): # in test/bases.py

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

278 '''

279 return _MODS.osversion2[0] is _iOS_

280

281

282 def _ismacOS(): # in test/bases.py

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

284 '''

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

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

287

288

289 def _isNix(): # in test/bases.py

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

291 '''

292 return _MODS.nix2[0]

293

294

295 def _isPyPy(): # in test/bases.py

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

297 '''

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

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

300

301

302 def _isWindows(): # in test/bases.py

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

304 '''

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

306 _MODS.osversion2[0] is _Windows_

307

308

309 def _load_lib(name):

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

311 '''

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

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

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

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

316 CDLL, dlopen, find_lib = _MODS.ctypes3

317

318 ns = find_lib(name), name

319 if dlopen is not _passarg: # _ismacOS()

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

321 _DOT_(name, 'framework'), _os_path.join(

322 _DOT_(name, 'framework'), name))

323 for n in ns:

324 try:

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

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

327 if lib._name: # has a qualified name

328 return lib

329 except (AttributeError, OSError):

330 pass

331

332 return None # raise OSError

333

334

335 def machine():

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

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

338

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

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

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

342 '''

343 return _MODS.bits_machine2[1]

344

345

346 def _name_version(pkg):

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

348 '''

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

350

351

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

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

354 '''

355 l2 = _MODS.osversion2

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

357

358

359 def _passarg(arg):

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

361 '''

362 return arg

363

364

365 def _passargs(*args):

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

367 '''

368 return args

369

370

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

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

373 '''

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

375

376

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

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

379

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

381 all positional.

382

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

384 '''

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

386

387

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

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

390

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

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

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

394 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments

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

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

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

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

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

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

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

402

403 @return: Number of bytes written.

404 '''

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

406 try:

407 if args:

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

409 elif kwds:

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

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

412 else:

413 t = fmt

414 except Exception as x:

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

416 unstr = _MODS.streprs.unstr

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

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

419 try:

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

421 except UnicodeEncodeError: # XXX only Windows

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

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

424 if fl: # PYCHOK no cover

425 f.flush()

426 return n

427

428

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

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

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

432 '''

433 if nl > 0:

434 prefix = NN(_NL_ * nl, prefix)

435 if nt > 0:

436 end = NN(end, _NL_ * nt)

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

438

439

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

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

442 '''

443 l3 = _MODS.Pythonarchine

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

445

446

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

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

449 '''

450 if secs < _MODS.constants._100_0:

451 unit = len(_SIsecs) - 1

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

453 secs *= 1e3 # _1000_0

454 unit -= 1

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

456 else:

457 m, s = divmod(secs, 60)

458 if m < 60:

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

460 else:

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

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

463 return t

464

465

466 def _sizeof(obj):

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

468

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

470 ignoring class attributes and

471 counting duplicates only once or

472 C{None}.

473

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

475 '''

476 try:

477 _zB = _sys.getsizeof

478 _zD = _zB(None) # some default

479 except TypeError: # PyPy3.10

480 return None

481

482 _isiterablen = _MODS.basics.isiterablen

483

484 def _zR(s, iterable):

485 z, _s = 0, s.add

486 for o in iterable:

487 i = id(o)

488 if i not in s:

489 _s(i)

490 z += _zB(o, _zD)

491 if isinstance(o, dict):

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

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

494 elif _isiterablen(o): # not map, ...

495 z += _zR(s, o)

496 else:

497 try: # size instance' attr values only

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

499 except AttributeError: # None, int, etc.

500 pass

501 return z

502

503 return _zR(set(), (obj,))

504

505

506 def _sysctl_uint(name):

507 '''(INTERNAL) Get an unsigned int sysctl item by name, use on macOS ONLY!

508 '''

509 libc = _MODS.libc

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

511 byref, char_p, size_t, uint, sizeof = _MODS.ctypes5

512 n = name if str is bytes else bytes(name, _utf_8_) # PYCHOK isPython2 = str is bytes

513 u = uint(0)

514 z = size_t(sizeof(u))

515 r = libc.sysctlbyname(char_p(n), byref(u), byref(z), None, size_t(0))

516 else: # could find or load 'libc'

517 r = -2

518 return int(r if r else u.value) # -1 ENOENT error, -2 no libc

519

520

521 def _tailof(name):

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

523 '''

524 i = name.rfind(_DOT_) + 1

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

526

527

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

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

530 '''

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

532

533

534 def _usage(file_py, *args, **opts_help): # in .etm, .geodesici

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

536 '''

537 if opts_help:

538

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

540 if alts and help:

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

542 for a in alts:

543 if a.startswith(h):

544 return NN(_DASH_, a),

545

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

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

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

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

550 if o:

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

552 d = NN

553 else:

554 d = _DASH_

555

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

557

558 u = _COLON_(_dunder_nameof(_usage)[1:], NN)

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

560

561

562 def _usage_argv(argv0, *args):

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

564 '''

565 m = _os_path.dirname(argv0).replace(_os.getcwd(), _ELLIPSIS_) \

566 .replace(_os.sep, _DOT_).strip()

567 b, x = _os_path.splitext(_os_path.basename(argv0))

568 if x == '.py' and not _dunder_ismain(b):

569 m = _DOT_(m or _pygeodesy_, b)

570 p = NN(_python_, _sys.version_info[0])

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

572

573

574 def _version2(version, n=2):

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

576 '''

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

578 if len(t) < n:

579 t += (0,) * n

580 return t[:n]

581

582

583 def _version_info(package): # in .Base.karney, .basics

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

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

586 '''

587 try:

588 return _version_ints(package.__version_info__)

589 except AttributeError:

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

591

592

593 def _version_ints(vs):

594 # helper for _version2 and _version_info above

595

596 def _ints(vs):

597 for v in vs:

598 try:

599 yield int(v.strip())

600 except (TypeError, ValueError):

601 pass

602

603 return tuple(_ints(vs))

604

605

606 def _versions(sep=_SPACE_):

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

608 '''

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

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

611

612

613__all__ = tuple(map(_dunder_nameof, (machine, print_, printf)))

614__version__ = '24.08.24'

615

616 if _dunder_ismain(__name__): # PYCHOK no cover

617

618 from pygeodesy import _isfrozen, isLazy

619

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

621 'isLazy', isLazy]))

622

623# **) MIT License

624#

625# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

626#

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

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

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

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

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

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

633#

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

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

636#

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

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

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

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

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

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

643# OTHER DEALINGS IN THE SOFTWARE.