Coverage for pygeodesy/internals.py: 87%
267 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-09-25 17:40 -0400
« prev ^ index » next coverage.py v7.6.0, created at 2024-09-25 17:40 -0400
1# -*- coding: utf-8 -*-
3u'''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
7from 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_
10from pygeodesy.interns import _COMMA_, _Python_ # PYCHOK used!
11# from pygeodesy.streprs import anstr, pairs, unstr # _MODS
13import os as _os # in .lazily, ...
14import os.path as _os_path
15# import sys as _sys # from .interns
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'
25def _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__
35def _dunder_nameof_(*names__): # in .errors._IsnotError
36 '''(INTERNAL) Yield the _dunder_nameof or name.
37 '''
38 return map(_dunder_nameof, names__, names__)
41def _Property_RO(method):
42 '''(INTERNAL) Can't I{recursively} import L{props.property_RO}.
43 '''
44 name = _dunder_nameof(method)
46 def _del(inst, attr): # PYCHOK no cover
47 delattr(inst, attr) # force error
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
57 def _set(inst, val): # PYCHOK no cover
58 setattr(inst, name, val) # force error
60 return property(_get, _set, _del)
63class _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)
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)
74 @_Property_RO
75 def bits_machine2(self):
76 '''Get platform 2-list C{[bits, machine]}, I{once}.
77 '''
78 import platform as p
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.
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
99 def dlopen(name):
100 return _dlopen(name, DEFAULT_MODE)
101 else: # PYCHOK no cover
102 from ctypes import CDLL
103 dlopen = _passarg
105 from ctypes.util import find_library
106 return CDLL, dlopen, find_library
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
115 def _DOT_(self, name): # PYCHOK no cover
116 return _DOT_(self.name, name)
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
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
134 @_Property_RO
135 def libc(self):
136 '''Load C{libc.dll|dylib}, I{once}.
137 '''
138 return _load_lib('libc')
140 @_Property_RO
141 def name(self):
142 '''Get this name (C{str}).
143 '''
144 return _dunder_nameof(self.__class__)
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
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
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()
171 @_Property_RO
172 def osversion2(self):
173 '''Get 2-list C{[OS, release]}, I{once}.
174 '''
175 import platform as p
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]
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
210 @_Property_RO
211 def _Str_Bytes(self):
212 '''Get all C{str} and C{bytes} types.
213 '''
214 import pygeodesy.basics as m
215 return m._Strs + m._Bytes # + (range, map)
217 @_Property_RO
218 def streprs(self):
219 '''Get module C{pygeodesy.streprs}, I{once}.
220 '''
221 from pygeodesy import streprs # DON'T _lazy_import2
222 return streprs
224 @_Property_RO
225 def version(self):
226 '''Get pygeodesy version, I{once}.
227 '''
228 from pygeodesy import version
229 return version
231_MODS = _MODS_Base() # PYCHOK overwritten by .lazily
234def _caller3(up): # in .lazily, .named
235 '''(INTERNAL) Get 3-tuple C{(caller name, file name, line number)}
236 for the caller B{C{up}} stack frames in the Python call stack.
237 '''
238 # sys._getframe(1) ... 'importlib._bootstrap' line 1032,
239 # may throw a ValueError('call stack not deep enough')
240 f = _sys._getframe(up + 1)
241 c = f.f_code
242 return (c.co_name, # caller name
243 _os_path.basename(c.co_filename), # file name .py
244 f.f_lineno) # line number
247def _dunder_ismain(name):
248 '''(INTERNAL) Return C{name == '__main__'}.
249 '''
250 return name == '__main__'
253def _enquote(strs, quote=_QUOTE2_, white=NN): # in .basics, .solveBase
254 '''(INTERNAL) Enquote a string containing whitespace or replace
255 whitespace by C{white} if specified.
256 '''
257 if strs:
258 t = strs.split()
259 if len(t) > 1:
260 strs = white.join(t if white else (quote, strs, quote))
261 return strs
264def _fper(p, q, per=100.0, prec=1):
265 '''Format a percentage C{B{p} * B{per} / B{q}} (C{str}).
266 '''
267 return '%.*f%%' % (prec, (float(p) * per / float(q)))
270def _headof(name):
271 '''(INTERNAL) Get the head name of qualified C{name} or the C{name}.
272 '''
273 i = name.find(_DOT_)
274 return name if i < 0 else name[:i]
277# def _is(a, b): # PYCHOK no cover
278# '''(INTERNAL) C{a is b}? in C{PyPy}
279# '''
280# return (a == b) if _isPyPy() else (a is b)
283def _isAppleM():
284 '''(INTERNAL) Is this C{Apple Silicon}? (C{bool})
285 '''
286 return _ismacOS() and machine().startswith(_arm64_)
289def _isiOS(): # in test/bases.py
290 '''(INTERNAL) Is this C{iOS}? (C{bool})
291 '''
292 return _MODS.osversion2[0] is _iOS_
295def _ismacOS(): # in test/bases.py
296 '''(INTERNAL) Is this C{macOS}? (C{bool})
297 '''
298 return _sys.platform[:6] == 'darwin' and \
299 _MODS.osversion2[0] is _macOS_ # and os.name == 'posix'
302def _isNix(): # in test/bases.py
303 '''(INTERNAL) Is this a C{Linux} distro? (C{str} or L{NN})
304 '''
305 return _MODS.nix2[0]
308def _isPyChecker():
309 '''(INTERNAL) Is C{PyChecker} running? (C{bool}).
310 '''
311 # .../pychecker/checker.py --limit 0 --stdlib pygeodesy/<mod>/<name>.py
312 return _sys.argv[0].endswith('/pychecker/checker.py')
315def _isPyPy(): # in test/bases.py
316 '''(INTERNAL) Is this C{PyPy}? (C{bool})
317 '''
318 # platform.python_implementation() == 'PyPy'
319 return _MODS.Pythonarchine[0].startswith(_PyPy__)
322def _isWindows(): # in test/bases.py
323 '''(INTERNAL) Is this C{Windows}? (C{bool})
324 '''
325 return _sys.platform[:3] == 'win' and \
326 _MODS.osversion2[0] is _Windows_
329def _load_lib(name):
330 '''(INTERNAL) Load a C{dylib}, B{C{name}} must startwith('lib').
331 '''
332 # macOS 11+ (aka 10.16) no longer provides direct loading of
333 # system libraries. As a result, C{ctypes.util.find_library}
334 # will not find any library, unless previously installed by a
335 # low-level dlopen(name) call (with the library base C{name}).
336 CDLL, dlopen, find_lib = _MODS.ctypes3
338 ns = find_lib(name), name
339 if dlopen is not _passarg: # _ismacOS()
340 ns += (_DOT_(name, 'dylib'),
341 _DOT_(name, 'framework'), _os_path.join(
342 _DOT_(name, 'framework'), name))
343 for n in ns:
344 try:
345 if n and dlopen(n): # pre-load handle
346 lib = CDLL(n) # == ctypes.cdll.LoadLibrary(n)
347 if lib._name: # has a qualified name
348 return lib
349 except (AttributeError, OSError):
350 pass
352 return None # raise OSError
355def machine():
356 '''Return standard C{platform.machine}, but distinguishing Intel I{native}
357 from Intel I{emulation} on Apple Silicon (on macOS only).
359 @return: Machine C{'arm64'} for Apple Silicon I{native}, C{'x86_64'}
360 for Intel I{native}, C{"arm64_x86_64"} for Intel I{emulation},
361 etc. (C{str} with C{comma}s replaced by C{underscore}s).
362 '''
363 return _MODS.bits_machine2[1]
366def _Math_K_2():
367 '''(INTERNAL) Return the I{Karney} Math setting.
368 '''
369 return _MODS.karney._wrapped.Math_K_2
372def _name_version(pkg):
373 '''(INTERNAL) Return C{pskg.__name__ + ' ' + .__version__}.
374 '''
375 return _SPACE_(pkg.__name__, pkg.__version__)
378def _name_binary(path):
379 '''(INTERNAL) Return C{(basename + ' ' + version)} of an executable.
380 '''
381 if path:
382 try:
383 _, r = _MODS.solveBase._popen2((path, '--version'))
384 return _SPACE_(_os_path.basename(path), r.split()[-1])
385 except (IndexError, IOError, OSError):
386 pass
387 return NN
390def _osversion2(sep=NN): # in .lazily, test/bases.versions
391 '''(INTERNAL) Get the O/S name and release as C{2-list} or C{str}.
392 '''
393 l2 = _MODS.osversion2
394 return sep.join(l2) if sep else l2 # 2-list()
397def _passarg(arg):
398 '''(INTERNAL) Helper, no-op.
399 '''
400 return arg
403def _passargs(*args):
404 '''(INTERNAL) Helper, no-op.
405 '''
406 return args
409def _plural(noun, n, nn=NN):
410 '''(INTERNAL) Return C{noun}['s'] or C{NN}.
411 '''
412 return NN(noun, _s_) if n > 1 else (noun if n else nn)
415def print_(*args, **nl_nt_prec_prefix__end_file_flush_sep__kwds): # PYCHOK no cover
416 '''Python 3+ C{print}-like formatting and printing.
418 @arg args: Values to be converted to C{str} and joined by B{C{sep}},
419 all positional.
421 @see: Function L{printf} for further details.
422 '''
423 return printf(NN, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
426def printf(fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds):
427 '''C{Printf-style} and Python 3+ C{print}-like formatting and printing.
429 @arg fmt: U{Printf-style<https://Docs.Python.org/3/library/stdtypes.html#
430 printf-style-string-formatting>} format specification (C{str}).
431 @arg args: Arguments to be formatted (any C{type}, all positional).
432 @kwarg nl_nt_prec_prefix__end_file_flush_sep__kwds: Optional keyword arguments
433 C{B{nl}=0} for the number of leading blank lines (C{int}), C{B{nt}=0}
434 the number of trailing blank lines (C{int}), C{B{prefix}=NN} to be
435 inserted before the formatted text (C{str}) and Python 3+ C{print}
436 keyword arguments C{B{end}}, C{B{sep}}, C{B{file}} and C{B{flush}}.
437 Any remaining C{B{kwds}} are C{printf-style} name-value pairs to be
438 formatted, I{iff no B{C{args}} are present} using C{B{prec}=6} for
439 the number of decimal digits (C{int}).
441 @return: Number of bytes written.
442 '''
443 b, e, f, fl, p, s, kwds = _print7(**nl_nt_prec_prefix__end_file_flush_sep__kwds)
444 try:
445 if args:
446 t = (fmt % args) if fmt else s.join(map(str, args))
447 elif kwds:
448 t = (fmt % kwds) if fmt else s.join(
449 _MODS.streprs.pairs(kwds, prec=p))
450 else:
451 t = fmt
452 except Exception as x:
453 _E, s = _MODS.errors._xError2(x)
454 unstr = _MODS.streprs.unstr
455 t = unstr(printf, fmt, *args, **nl_nt_prec_prefix__end_file_flush_sep__kwds)
456 raise _E(s, txt=t, cause=x)
457 try:
458 n = f.write(NN(b, t, e))
459 except UnicodeEncodeError: # XXX only Windows
460 t = t.replace('\u2032', _QUOTE1_).replace('\u2033', _QUOTE2_)
461 n = f.write(NN(b, t, e))
462 if fl: # PYCHOK no cover
463 f.flush()
464 return n
467def _print7(nl=0, nt=0, prec=6, prefix=NN, sep=_SPACE_, file=_sys.stdout,
468 end=_NL_, flush=False, **kwds):
469 '''(INTERNAL) Unravel the C{printf} and remaining keyword arguments.
470 '''
471 if nl > 0:
472 prefix = NN(_NL_ * nl, prefix)
473 if nt > 0:
474 end = NN(end, _NL_ * nt)
475 return prefix, end, file, flush, prec, sep, kwds
478def _Pythonarchine(sep=NN): # in .lazily, test/bases.py versions
479 '''(INTERNAL) Get PyPy and Python versions, bits and machine as C{3- or 4-list} or C{str}.
480 '''
481 l3 = _MODS.Pythonarchine
482 return sep.join(l3) if sep else l3 # 3- or 4-list
485def _secs2str(secs): # in .geoids, ../test/bases.py
486 '''Convert a time in C{secs} to C{str}.
487 '''
488 if secs < _MODS.constants._100_0:
489 unit = len(_SIsecs) - 1
490 while 0 < secs < 1 and unit > 0:
491 secs *= 1e3 # _1000_0
492 unit -= 1
493 t = '%.3f %s' % (secs, _SIsecs[unit])
494 else:
495 m, s = divmod(secs, 60)
496 if m < 60:
497 t = '%d:%06.3f' % (int(m), s)
498 else:
499 h, m = divmod(int(m), 60)
500 t = '%d:%02d:%06.3f' % (h, m, s)
501 return t
504def _sizeof(obj, deep=True):
505 '''(INTERNAL) Recursively size an C{obj}ect.
507 @kwarg deep: If C{True}, include the size of all
508 C{.__dict__.values()} (C{bool}).
510 @return: The C{obj} size in bytes (C{int}), ignoring
511 class attributes and counting instances only
512 once or C{None}.
514 @note: With C{PyPy}, the returned size is always C{None}.
515 '''
516 try:
517 _zB = _sys.getsizeof
518 _zD = _zB(None) # some default
519 except TypeError: # PyPy3.10
520 return None
522 _isiterablen = _MODS.basics.isiterablen
524 def _zR(s, iterable):
525 z, _s = 0, s.add
526 for o in iterable:
527 i = id(o)
528 if i not in s:
529 _s(i)
530 z += _zB(o, _zD)
531 if isinstance(o, dict):
532 z += _zR(s, o.keys())
533 z += _zR(s, o.values())
534 elif _isiterablen(o) and not \
535 isinstance(o, _MODS._Str_Bytes):
536 z += _zR(s, o)
537 elif deep:
538 try: # size instance' attr values only
539 z += _zR(s, o.__dict__.values())
540 except AttributeError: # None, int, etc.
541 pass
542 return z
544 return _zR(set(), (obj,))
547def _sysctl_uint(name):
548 '''(INTERNAL) Get an unsigned int sysctl item by name, use on macOS ONLY!
549 '''
550 libc = _MODS.libc
551 if libc: # <https://StackOverflow.com/questions/759892/python-ctypes-and-sysctl>
552 byref, char_p, size_t, uint, sizeof = _MODS.ctypes5
553 n = name if str is bytes else bytes(name, _utf_8_) # PYCHOK isPython2 = str is bytes
554 u = uint(0)
555 z = size_t(sizeof(u))
556 r = libc.sysctlbyname(char_p(n), byref(u), byref(z), None, size_t(0))
557 else: # could find or load 'libc'
558 r = -2
559 return int(r if r else u.value) # -1 ENOENT error, -2 no libc
562def _tailof(name):
563 '''(INTERNAL) Get the base name of qualified C{name} or the C{name}.
564 '''
565 i = name.rfind(_DOT_) + 1
566 return name[i:] if i > 0 else name
569def _under(name): # PYCHOK in .datums, .auxilats, .ups, .utm, .utmupsBase, ...
570 '''(INTERNAL) Prefix C{name} with an I{underscore}.
571 '''
572 return name if name.startswith(_UNDER_) else NN(_UNDER_, name)
575def _usage(file_py, *args, **opts_help): # in .etm, .geodesici
576 '''(INTERNAL) Build "usage: python -m ..." cmd line for module B{C{file_py}}.
577 '''
578 if opts_help:
580 def _help(alts=(), help=NN, **unused):
581 if alts and help:
582 h = NN(help, _SPACE_).lstrip(_DASH_)
583 for a in alts:
584 if a.startswith(h):
585 return NN(_DASH_, a),
587 def _opts(opts=NN, alts=(), **unused):
588 # opts='T--v-C-R meter-c|i|n|o'
589 d, fmt = NN, _MODS.streprs.Fmt.SQUARE
590 for o in (opts + _BAR_(*alts)).split(_DASH_):
591 if o:
592 yield fmt(NN(d, _DASH_, o.replace(_BAR_, ' | -')))
593 d = NN
594 else:
595 d = _DASH_
597 args = _help(**opts_help) or (tuple(_opts(**opts_help)) + args)
599 u = _COLON_(_dunder_nameof(_usage)[1:], NN)
600 return _SPACE_(u, *_usage_argv(file_py, *args))
603def _usage_argv(argv0, *args):
604 '''(INTERNAL) Return 3-tuple C{(python, '-m', module, *args)}.
605 '''
606 m = _os_path.dirname(argv0).replace(_os.getcwd(), _ELLIPSIS_) \
607 .replace(_os.sep, _DOT_).strip()
608 b, x = _os_path.splitext(_os_path.basename(argv0))
609 if x == '.py' and not _dunder_ismain(b):
610 m = _DOT_(m or _pygeodesy_, b)
611 p = NN(_python_, _sys.version_info[0])
612 return (p, '-m', _enquote(m)) + args
615def _version2(version, n=2):
616 '''(INTERNAL) Split C{B{version} str} into a C{1-, 2- or 3-tuple} of C{int}s.
617 '''
618 t = _version_ints(version.split(_DOT_, 2))
619 if len(t) < n:
620 t += (0,) * n
621 return t[:n]
624def _version_info(package): # in .Base.karney, .basics
625 '''(INTERNAL) Get the C{package.__version_info__} as a 2- or
626 3-tuple C{(major, minor, revision)} if C{int}s.
627 '''
628 try:
629 return _version_ints(package.__version_info__)
630 except AttributeError:
631 return _version2(package.__version__.strip(), n=3)
634def _version_ints(vs):
635 # helper for _version2 and _version_info above
637 def _ints(vs):
638 for v in vs:
639 try:
640 yield int(v.strip())
641 except (TypeError, ValueError):
642 pass
644 return tuple(_ints(vs))
647def _versions(sep=_SPACE_):
648 '''(INTERNAL) Get pygeodesy, PyPy and Python versions, bits, machine and OS as C{7- or 8-list} or C{str}.
649 '''
650 l7 = [_pygeodesy_, _MODS.version] + _Pythonarchine() + _osversion2()
651 return sep.join(l7) if sep else l7 # 5- or 6-list
654__all__ = tuple(map(_dunder_nameof, (machine, print_, printf)))
655__version__ = '24.09.04'
657if _dunder_ismain(__name__): # PYCHOK no cover
659 from pygeodesy import _isfrozen, isLazy
661 print_(*(_versions(sep=NN) + ['_isfrozen', _isfrozen,
662 'isLazy', isLazy]))
664# **) MIT License
665#
666# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
667#
668# Permission is hereby granted, free of charge, to any person obtaining a
669# copy of this software and associated documentation files (the "Software"),
670# to deal in the Software without restriction, including without limitation
671# the rights to use, copy, modify, merge, publish, distribute, sublicense,
672# and/or sell copies of the Software, and to permit persons to whom the
673# Software is furnished to do so, subject to the following conditions:
674#
675# The above copyright notice and this permission notice shall be included
676# in all copies or substantial portions of the Software.
677#
678# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
679# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
680# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
681# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
682# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
683# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
684# OTHER DEALINGS IN THE SOFTWARE.