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