Coverage for pygeodesy/streprs.py: 95%
268 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-07-12 13:40 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-07-12 13:40 -0400
2# -*- coding: utf-8 -*-
4u'''Floating point and other formatting utilities.
5'''
7from pygeodesy.basics import _0_0, isint, islistuple, isscalar, isstr, _zip
8# from pygeodesy.constants import _0_0
9from pygeodesy.errors import _AttributeError, _IsnotError, itemsorted, _or, \
10 _TypeError, _ValueError, _xkwds_get, _xkwds_pop
11from pygeodesy.interns import NN, _0_, _0to9_, MISSING, _BAR_, _COMMASPACE_, \
12 _DOT_, _dunder_nameof, _E_, _ELLIPSIS_, _EQUAL_, \
13 _H_, _LR_PAIRS, _N_, _name_, _not_, _not_scalar_, \
14 _PERCENT_, _SPACE_, _STAR_, _UNDER_
15from pygeodesy.interns import _convergence_, _distant_, _e_, _eps_, _exceeds_, \
16 _EQUALSPACED_, _f_, _F_, _g_, _limit_, _no_, \
17 _tolerance_ # PYCHOK used!
18from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
20from math import fabs, log10 as _log10
22__all__ = _ALL_LAZY.streprs
23__version__ = '23.06.12'
25_EN_PREC = 6 # max MGRS/OSGR precision, 1 micrometer
26_EN_WIDE = 5 # number of MGRS/OSGR units, log10(_100km)
27_OKd_ = '._-' # acceptable name characters
28_PAREN_g = '(%g)' # PYCHOK used!
29_threshold_ = 'threshold' # PYCHOK used!
32class _Fmt(str): # in .streprs
33 '''(INTERNAL) Callable formatting.
34 '''
35 name = NN
37 def __call__(self, *name_value_, **name_value):
38 '''Format a C{name=value} pair or C{name, value} pair
39 or just a single C{value}.
40 '''
41 for n, v in name_value.items():
42 break
43 else:
44 if len(name_value_) > 1:
45 n, v = name_value_[:2]
46 elif name_value_:
47 n, v = NN, name_value_[0]
48 else:
49 n, v = NN, MISSING
50 t = str.__mod__(self, v)
51 return NN(n, t) if n else t
53# def __mod__(self, arg, **unused):
54# '''Regular C{%} operator.
55# '''
56# return str.__mod__(self, arg)
59class Fstr(str):
60 '''(INTERNAL) C{float} format.
61 '''
62 name = NN
64 def __call__(self, flt, prec=None, ints=False):
65 '''Format the B{C{flt}} like function L{fstr}.
66 '''
67 # see also function C{fstr} if isscalar case below
68 t = str.__mod__(_pct(self), flt) if prec is None else next(
69 _streprs(prec, (flt,), self, ints, True, None))
70 return t
72 def __mod__(self, arg, **unused):
73 '''Regular C{%} operator.
75 @arg arg: A C{scalar} value to be formatted (either
76 the C{scalar}, or a 1-tuple C{(scalar,)},
77 or 2-tuple C{(prec, scalar)}.
79 @raise TypeError: Non-scalar B{C{arg}} value.
81 @raise ValueError: Invalid B{C{arg}}.
82 '''
83 def _error(arg):
84 n = _DOT_(Fstr.__name__, self.name or self)
85 return _SPACE_(n, _PERCENT_, repr(arg))
87 prec = 6 # default std %f and %F
88 if islistuple(arg):
89 n = len(arg)
90 if n == 1:
91 arg = arg[0]
92 elif n == 2:
93 prec, arg = arg
94 else:
95 raise _ValueError(_error(arg))
97 if not isscalar(arg):
98 raise _TypeError(_error(arg))
99 return self(arg, prec=prec)
102class _Sub(str):
103 '''(INTERNAL) Class list formatter.
104 '''
105 # see .ellipsoidalNvector.LatLon.deltaTo
106 def __call__(self, *Classes):
107 t = _or(*(C.__name__ for C in Classes))
108 return str.__mod__(self, t or MISSING)
111class Fmt(object):
112 '''Formatting options.
113 '''
114 ANGLE = _Fmt('<%s>')
115 COLON = _Fmt(':%s')
116# COLONSPACE = _Fmt(': %s') # == _COLONSPACE_(n, v)
117# COMMASPACE = _Fmt(', %s') # == _COMMASPACE_(n, v)
118 convergence = _Fmt(_convergence_(_PAREN_g))
119 CURLY = _Fmt('{%s}') # BRACES
120 distant = _Fmt(_distant_('(%.3g)'))
121 DOT = _Fmt('.%s') # == NN(_DOT_, n)
122 e = Fstr(_e_)
123 E = Fstr(_E_)
124 EQUAL = _Fmt(_EQUAL_(NN, '%s'))
125 EQUALg = _Fmt(_EQUAL_(NN, '%g'))
126 EQUALSPACED = _Fmt(_EQUALSPACED_(NN, '%s'))
127 exceeds_eps = _Fmt(_exceeds_(_eps_, _PAREN_g))
128 exceeds_limit = _Fmt(_exceeds_(_limit_, _PAREN_g))
129 f = Fstr(_f_)
130 F = Fstr(_F_)
131 g = Fstr(_g_)
132 G = Fstr('G')
133 h = Fstr('%+.*f') # height, .streprs.hstr
134 limit = _Fmt(' %s limit') # .units
135 LOPEN = _Fmt('(%s]') # left-open range (L, R]
136 PAREN = _Fmt('(%s)')
137 PAREN_g = _Fmt(_PAREN_g)
138 PARENSPACED = _Fmt(' (%s)')
139 QUOTE2 = _Fmt('"%s"')
140 ROPEN = _Fmt('[%s)') # right-open range [L, R)
141# SPACE = _Fmt(' %s') # == _SPACE_(n, v)
142 SQUARE = _Fmt('[%s]') # BRACKETS
143 sub_class = _Sub('%s (sub-)class')
144 TAG = ANGLE
145 TAGEND = _Fmt('</%s>')
146 tolerance = _Fmt(_tolerance_(_PAREN_g))
147 zone = _Fmt('%02d') # .epsg, .mgrs, .utmupsBase
149 def __init__(self):
150 for n, a in self.__class__.__dict__.items():
151 if isinstance(a, (Fstr, _Fmt)):
152 setattr(a, _name_, n)
154 def __call__(self, obj, prec=9):
155 '''Return C{str(B{obj})} or C{repr(B{obj})}.
156 '''
157 return str(obj) if isint(obj) else next(
158 _streprs(prec, (obj,), Fmt.g, False, False, repr))
160 def no_convergence(self, _d, *tol, **thresh):
161 t = Fmt.convergence(fabs(_d))
162 if tol:
163 t = _COMMASPACE_(t, Fmt.tolerance(tol[0]))
164 if thresh and _xkwds_get(thresh, thresh=False):
165 t = t.replace(_tolerance_, _threshold_)
166 return _no_(t)
168Fmt = Fmt() # PYCHOK singleton
169Fmt.__name__ = Fmt.__class__.__name__
171_DOTSTAR_ = Fmt.DOT(_STAR_)
172# formats %G and %g drop all trailing zeros and the
173# decimal point, making the float appear as an int
174_Gg = (Fmt.G, Fmt.g)
175_FfEeGg = (Fmt.F, Fmt.f, Fmt.E, Fmt.e) + _Gg # float formats
176_Fspec_ = NN('[%[<flags>][<width>]', _DOTSTAR_, ']', _BAR_.join(_FfEeGg)) # in testStreprs
179def anstr(name, OKd=_OKd_, sub=_UNDER_):
180 '''Make a valid name of alphanumeric and OKd characters.
182 @arg name: The original name (C{str}).
183 @kwarg OKd: Other acceptable characters (C{str}).
184 @kwarg sub: Substitute for invalid charactes (C{str}).
186 @return: The modified name (C{str}).
188 @note: Leading and trailing whitespace characters are removed,
189 intermediate whitespace characters are coalesced and
190 substituted.
191 '''
192 s = n = str(name).strip()
193 for c in n:
194 if not (c.isalnum() or c in OKd or c in sub):
195 s = s.replace(c, _SPACE_)
196 return sub.join(s.strip().split())
199def attrs(inst, *names, **Nones_True__pairs_kwds): # prec=6, fmt=Fmt.F, ints=False, Nones=True, sep=_EQUAL_
200 '''Get instance attributes as I{name=value} strings, with C{float}s
201 formatted by function L{fstr}.
203 @arg inst: The instance (any C{type}).
204 @arg names: The attribute names, all other positional (C{str}).
205 @kwarg Nones_True__pairs_kwds: Keyword argument for function L{pairs}, except
206 C{B{Nones}=True} to in-/exclude missing or C{None}-valued attributes.
208 @return: A C{tuple(B{sep}.join(t) for t in zip(B{names}, reprs(values, ...)))}
209 of C{str}s.
210 '''
211 def _items(inst, names, Nones):
212 for n in names:
213 v = getattr(inst, n, None)
214 if Nones or v is not None:
215 yield n, v
217 def _Nones_kwds(Nones=True, **kwds):
218 return Nones, kwds
220 Nones, kwds = _Nones_kwds(**Nones_True__pairs_kwds)
221 return pairs(_items(inst, names, Nones), **kwds)
224def enstr2(easting, northing, prec, *extras, **wide_dot):
225 '''Return an MGRS/OSGR easting, northing string representations.
227 @arg easting: Easting from false easting (C{meter}).
228 @arg northing: Northing from from false northing (C{meter}).
229 @arg prec: Precision, the number of I{decimal} digits (C{int}) or if
230 negative, the number of I{units to drop}, like MGRS U{PRECISION
231 <https://GeographicLib.SourceForge.io/C++/doc/GeoConvert.1.html#PRECISION>}.
232 @arg extras: Optional leading items (C{str}s).
233 @kwarg wide_dot: Optional keword argument C{B{wide}=%d} for the number of I{unit digits}
234 (C{int}) and C{B{dot}=False} (C{bool}) to insert a decimal point.
236 @return: B{C{extras}} + 2-tuple C{(str(B{easting}), str(B{northing}))} or
237 + 2-tuple C{("", "")} for C{B{prec} <= -B{wide}}.
239 @raise ValueError: Invalid B{C{easting}}, B{C{northing}} or B{C{prec}}.
241 @note: The B{C{easting}} and B{C{northing}} values are I{truncated, not rounded}.
242 '''
243 t = extras
244 try: # like .dms.compassPoint
245 p = min(int(prec), _EN_PREC)
246 w = p + _xkwds_get(wide_dot, wide=_EN_WIDE)
247 if w > 0:
248 f = 10**p # truncate
249 d = (-p) if p > 0 and _xkwds_get(wide_dot, dot=False) else 0
250 t += (_0wdot(w, int(easting * f), d),
251 _0wdot(w, int(northing * f), d))
252 else: # prec <= -_EN_WIDE
253 t += (NN, NN)
254 except (TypeError, ValueError) as x:
255 raise _ValueError(easting=easting, northing=northing, prec=prec, cause=x)
256 return t
258if enstr2.__doc__: # PYCHOK expected
259 enstr2.__doc__ %= (_EN_WIDE,)
262def _enstr2m3(estr, nstr, wide=_EN_WIDE): # in .mgrs, .osgr
263 '''(INTERNAL) Convert east- and northing C{str}s to meter and resolution.
264 '''
265 def _s2m2(s, m): # e or n str to float meter
266 if _DOT_ in s:
267 m = 1 # meter
268 else:
269 s += _0_ * wide
270 s = _DOT_(s[:wide], s[wide:wide+_EN_PREC])
271 return float(s), m
273 e, m = _s2m2(estr, 0)
274 n, m = _s2m2(nstr, m)
275 if not m:
276 p = max(len(estr), len(nstr)) # 2 = Km, 5 = m, 7 = cm
277 m = 10**max(-_EN_PREC, wide - p) # resolution, meter
278 return e, n, m
281def fstr(floats, prec=6, fmt=Fmt.F, ints=False, sep=_COMMASPACE_, strepr=None):
282 '''Convert one or more floats to string, optionally stripped of trailing zero decimals.
284 @arg floats: Single or a list, sequence, tuple, etc. (C{scalar}s).
285 @kwarg prec: The C{float} precision, number of decimal digits (0..9).
286 Trailing zero decimals are stripped if B{C{prec}} is
287 positive, but kept for negative B{C{prec}} values. In
288 addition, trailing decimal zeros are stripped for U{alternate,
289 form '#'<https://docs.Python.org/3/library/stdtypes.html
290 #printf-style-string-formatting>}.
291 @kwarg fmt: Optional, C{float} format (C{str}).
292 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).
293 @kwarg sep: Separator joining the B{C{floats}} (C{str}).
294 @kwarg strepr: Optional callable to format non-C{floats} (typically
295 C{repr}, C{str}) or C{None} to raise a TypeError.
297 @return: The C{sep.join(strs(floats, ...)} joined (C{str}) or single
298 C{strs((floats,), ...)} (C{str}) if B{C{floats}} is C{scalar}.
299 '''
300 if isscalar(floats): # see Fstr.__call__ above
301 return next(_streprs(prec, (floats,), fmt, ints, True, strepr))
302 else:
303 return sep.join(_streprs(prec, floats, fmt, ints, True, strepr))
306def _fstrENH2(inst, prec, m): # in .css, .lcc, .utmupsBase
307 # (INTERNAL) For C{Css.} and C{Lcc.} C{toRepr} and C{toStr} and C{UtmUpsBase._toStr}.
308 t = inst.easting, inst.northing
309 t = tuple(_streprs(prec, t, Fmt.F, False, True, None))
310 T = _E_, _N_
311 if m is not None and fabs(inst.height): # fabs(self.height) > EPS
312 t += hstr(inst.height, prec=-2, m=m),
313 T += _H_,
314 return t, T
317def _fstrLL0(inst, prec, toRepr): # in .azimuthal, .css
318 # (INTERNAL) For C{_AlbersBase.}, C{_AzimuthalBase.} and C{CassiniSoldner.}
319 t = tuple(_streprs(prec, inst.latlon0, Fmt.F, False, True, None))
320 if toRepr:
321 n = inst.name
322 if n:
323 t += Fmt.EQUAL(_name_, repr(n)),
324 t = Fmt.PAREN(inst.classname, _COMMASPACE_.join(t))
325 return t
328def fstrzs(efstr, ap1z=False):
329 '''Strip trailing zero decimals from a C{float} string.
331 @arg efstr: Float with or without exponent (C{str}).
332 @kwarg ap1z: Append the decimal point and one zero decimal
333 if the B{C{efstr}} is all digits (C{bool}).
335 @return: Float (C{str}).
336 '''
337 s = efstr.find(_DOT_)
338 if s >= 0:
339 e = efstr.rfind(Fmt.e)
340 if e < 0:
341 e = efstr.rfind(Fmt.E)
342 if e < 0:
343 e = len(efstr)
344 s += 2 # keep 1st _DOT_ + _0_
345 if s < e and efstr[e-1] == _0_:
346 efstr = NN(efstr[:s], efstr[s:e].rstrip(_0_), efstr[e:])
348 elif ap1z:
349 # %.G and %.g formats may drop the decimal
350 # point and all trailing zeros, ...
351 if efstr.isdigit():
352 efstr += _DOT_ + _0_ # ... append or ...
353 else: # ... insert one dot and zero
354 e = efstr.rfind(Fmt.e)
355 if e < 0:
356 e = efstr.rfind(Fmt.E)
357 if e > 0:
358 efstr = NN(efstr[:e], _DOT_, _0_, efstr[e:])
360 return efstr
363def hstr(height, prec=2, fmt=Fmt.h, ints=False, m=NN):
364 '''Return a string for the height value.
366 @arg height: Height value (C{float}).
367 @kwarg prec: The C{float} precision, number of decimal digits (0..9).
368 Trailing zero decimals are stripped if B{C{prec}} is
369 positive, but kept for negative B{C{prec}} values.
370 @kwarg fmt: Optional, C{float} format (C{str}).
371 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).
372 @kwarg m: Optional unit of the height (C{str}).
373 '''
374 h = next(_streprs(prec, (height,), fmt, ints, True, None))
375 return NN(h, str(m)) if m else h
378def instr(inst, *args, **kwds):
379 '''Return the string representation of an instantiation.
381 @arg inst: The instance (any C{type}).
382 @arg args: Optional positional arguments.
383 @kwarg kwds: Optional keyword arguments.
385 @return: Representation (C{str}).
386 '''
387 return unstr(_MODS.named.classname(inst), *args, **kwds)
390def lrstrip(txt, lrpairs=_LR_PAIRS):
391 '''Left- I{and} right-strip parentheses, brackets, etc. from a string.
393 @arg txt: String to be stripped (C{str}).
394 @kwarg lrpairs: Parentheses, etc. to remove (C{dict} of one or several
395 C{(Left, Right)} pairs).
397 @return: Stripped B{C{txt}} (C{str}).
398 '''
399 _e, _s, _n = str.endswith, str.startswith, len
400 while _n(txt) > 2:
401 for L, R in lrpairs.items():
402 if _e(txt, R) and _s(txt, L):
403 txt = txt[_n(L):-_n(R)]
404 break # restart
405 else:
406 return txt
409def pairs(items, prec=6, fmt=Fmt.F, ints=False, sep=_EQUAL_):
410 '''Convert items to I{name=value} strings, with C{float}s handled like L{fstr}.
412 @arg items: Name-value pairs (C{dict} or 2-{tuple}s of any C{type}s).
413 @kwarg prec: The C{float} precision, number of decimal digits (0..9).
414 Trailing zero decimals are stripped if B{C{prec}} is
415 positive, but kept for negative B{C{prec}} values.
416 @kwarg fmt: Optional, C{float} format (C{str}).
417 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).
418 @kwarg sep: Separator joining I{names} and I{values} (C{str}).
420 @return: A C{tuple(B{sep}.join(t) for t in B{items}))} of C{str}s.
421 '''
422 try:
423 if isinstance(items, dict):
424 items = itemsorted(items)
425 elif not islistuple(items):
426 items = tuple(items)
427 # can't unzip empty items tuple, list, etc.
428 n, v = _zip(*items) if items else ((), ()) # strict=True
429 except (TypeError, ValueError):
430 raise _IsnotError(dict.__name__, '2-tuples', items=items)
431 v = _streprs(prec, v, fmt, ints, False, repr)
432 return tuple(sep.join(t) for t in _zip(map(str, n), v)) # strict=True
435def _pct(fmt):
436 '''(INTERNAL) Prefix C{%} if needed.
437 '''
438 return fmt if _PERCENT_ in fmt else NN(_PERCENT_, fmt)
441def reprs(objs, prec=6, fmt=Fmt.F, ints=False):
442 '''Convert objects to C{repr} strings, with C{float}s handled like L{fstr}.
444 @arg objs: List, sequence, tuple, etc. (any C{type}s).
445 @kwarg prec: The C{float} precision, number of decimal digits (0..9).
446 Trailing zero decimals are stripped if B{C{prec}} is
447 positive, but kept for negative B{C{prec}} values.
448 @kwarg fmt: Optional, C{float} format (C{str}).
449 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).
451 @return: A C{tuple(map(fstr|repr, objs))} of C{str}s.
452 '''
453 return tuple(_streprs(prec, objs, fmt, ints, False, repr)) if objs else ()
456def _resolution10(resolution, Error=ValueError): # in .mgrs, .osgr
457 '''(INTERNAL) Validate C{resolution} in C{meter}.
458 '''
459 try:
460 r = int(_log10(resolution))
461 if _EN_WIDE < r or r < -_EN_PREC:
462 raise ValueError
463 except (ValueError, TypeError):
464 raise Error(resolution=resolution)
465 return _MODS.units.Meter(resolution=10**r)
468def _streprs(prec, objs, fmt, ints, force, strepr):
469 '''(INTERNAL) Helper for C{fstr}, C{pairs}, C{reprs} and C{strs}
470 '''
471 # <https://docs.Python.org/3/library/stdtypes.html#printf-style-string-formatting>
472 if fmt in _FfEeGg:
473 fGg = fmt in _Gg
474 fmt = NN(_PERCENT_, _DOT_, abs(prec), fmt)
476 elif fmt.startswith(_PERCENT_):
477 fGg = False
478 try: # to make sure fmt is valid
479 f = fmt.replace(_DOTSTAR_, Fmt.DOT(abs(prec)))
480 _ = f % (_0_0,)
481 except (TypeError, ValueError):
482 raise _ValueError(fmt=fmt, txt=_not_(repr(_DOTSTAR_)))
483 fmt = f
485 else:
486 raise _ValueError(fmt=fmt, txt=_not_(repr(_Fspec_)))
488 for i, o in enumerate(objs):
489 if force or isinstance(o, float):
490 t = fmt % (float(o),)
491 if ints and t.rstrip(_0to9_ if isint(o, both=True) else
492 _0_).endswith(_DOT_):
493 t = t.split(_DOT_)[0]
494 elif prec > 1:
495 t = fstrzs(t, ap1z=fGg)
496 elif strepr:
497 t = strepr(o)
498 else:
499 t = Fmt.PARENSPACED(Fmt.SQUARE(objs=i), o)
500 raise TypeError(_SPACE_(t, _not_scalar_))
501 yield t
504def strs(objs, prec=6, fmt=Fmt.F, ints=False):
505 '''Convert objects to C{str} strings, with C{float}s handled like L{fstr}.
507 @arg objs: List, sequence, tuple, etc. (any C{type}s).
508 @kwarg prec: The C{float} precision, number of decimal digits (0..9).
509 Trailing zero decimals are stripped if B{C{prec}} is
510 positive, but kept for negative B{C{prec}} values.
511 @kwarg fmt: Optional, C{float} format (C{str}).
512 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).
514 @return: A C{tuple(map(fstr|str, objs))} of C{str}s.
515 '''
516 return tuple(_streprs(prec, objs, fmt, ints, False, str)) if objs else ()
519def unstr(where, *args, **kwds):
520 '''Return the string representation of an invokation.
522 @arg where: Class, function, method (C{type}) or name (C{str}).
523 @arg args: Optional positional arguments.
524 @kwarg kwds: Optional keyword arguments, except
525 C{B{_ELLIPSIS}=False}.
527 @return: Representation (C{str}).
528 '''
529 t = reprs(args, fmt=Fmt.g) if args else ()
530 if kwds and _xkwds_pop(kwds, _ELLIPSIS=False):
531 t += _ELLIPSIS_,
532 if kwds:
533 t += pairs(itemsorted(kwds), fmt=Fmt.g)
534 n = where if isstr(where) else _dunder_nameof(where)
535 return Fmt.PAREN(n, _COMMASPACE_.join(t))
538def _0wd(*w_i): # in .osgr, .wgrs
539 '''(INTERNAL) Int formatter'.
540 '''
541 return '%0*d' % w_i
544def _0wdot(w, f, dot=0):
545 '''(INTERNAL) Int and Float formatter'.
546 '''
547 s = _0wd(w, int(f))
548 if dot:
549 s = _DOT_(s[:dot], s[dot:])
550 return s
553def _0wpF(*w_p_f): # in .dms, .osgr
554 '''(INTERNAL) Float deg, min, sec formatter'.
555 '''
556 return '%0*.*f' % w_p_f # XXX was F
559def _xattrs(insto, other, *attrs): # see .errors._xattr
560 '''(INTERNAL) Copy attribute values from B{C{other}} to B{C{insto}}.
562 @arg insto: Object to copy attribute values to (any C{type}).
563 @arg other: Object to copy attribute values from (any C{type}).
564 @arg attrs: One or more attribute names (C{str}s).
566 @return: Object B{C{insto}}, updated.
568 @raise AttributeError: An B{C{attrs}} doesn't exist
569 or is not settable.
570 '''
571 def _getattr(o, a):
572 if hasattr(o, a):
573 return getattr(o, a)
574 try:
575 n = o._DOT_(a)
576 except AttributeError:
577 n = Fmt.DOT(a)
578 raise _AttributeError(o, name=n)
580 for a in attrs:
581 s = _getattr(other, a)
582 g = _getattr(insto, a)
583 if (g is None and s is not None) or g != s:
584 setattr(insto, a, s) # not settable?
585 return insto
588def _xzipairs(names, values, sep=_COMMASPACE_, fmt=NN, pair_fmt=Fmt.COLON):
589 '''(INTERNAL) Zip C{names} and C{values} into a C{str}, joined and bracketed.
590 '''
591 try:
592 t = sep.join(pair_fmt(*t) for t in _zip(names, values)) # strict=True
593 except Exception as x:
594 raise _ValueError(names=names, values=values, cause=x)
595 return (fmt % (t,)) if fmt else t
597# **) MIT License
598#
599# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
600#
601# Permission is hereby granted, free of charge, to any person obtaining a
602# copy of this software and associated documentation files (the "Software"),
603# to deal in the Software without restriction, including without limitation
604# the rights to use, copy, modify, merge, publish, distribute, sublicense,
605# and/or sell copies of the Software, and to permit persons to whom the
606# Software is furnished to do so, subject to the following conditions:
607#
608# The above copyright notice and this permission notice shall be included
609# in all copies or substantial portions of the Software.
610#
611# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
612# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
613# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
614# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
615# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
616# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
617# OTHER DEALINGS IN THE SOFTWARE.