Coverage for pygeodesy/streprs.py: 96%

268 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-10-11 16:04 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Floating point and other formatting utilities. 

5''' 

6 

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 

19 

20from math import fabs, log10 as _log10 

21 

22__all__ = _ALL_LAZY.streprs 

23__version__ = '23.06.12' 

24 

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! 

30 

31 

32class _Fmt(str): # in .streprs 

33 '''(INTERNAL) Callable formatting. 

34 ''' 

35 name = NN 

36 

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 

52 

53# def __mod__(self, arg, **unused): 

54# '''Regular C{%} operator. 

55# ''' 

56# return str.__mod__(self, arg) 

57 

58 

59class Fstr(str): 

60 '''(INTERNAL) C{float} format. 

61 ''' 

62 name = NN 

63 

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 

71 

72 def __mod__(self, arg, **unused): 

73 '''Regular C{%} operator. 

74 

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)}. 

78 

79 @raise TypeError: Non-scalar B{C{arg}} value. 

80 

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)) 

86 

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)) 

96 

97 if not isscalar(arg): 

98 raise _TypeError(_error(arg)) 

99 return self(arg, prec=prec) 

100 

101 

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) 

109 

110 

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 

148 

149 def __init__(self): 

150 for n, a in self.__class__.__dict__.items(): 

151 if isinstance(a, (Fstr, _Fmt)): 

152 setattr(a, _name_, n) 

153 

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)) 

159 

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) 

167 

168Fmt = Fmt() # PYCHOK singleton 

169Fmt.__name__ = Fmt.__class__.__name__ 

170 

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 

177 

178 

179def anstr(name, OKd=_OKd_, sub=_UNDER_): 

180 '''Make a valid name of alphanumeric and OKd characters. 

181 

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}). 

185 

186 @return: The modified name (C{str}). 

187 

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()) 

197 

198 

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}. 

202 

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. 

207 

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 

216 

217 def _Nones_kwds(Nones=True, **kwds): 

218 return Nones, kwds 

219 

220 Nones, kwds = _Nones_kwds(**Nones_True__pairs_kwds) 

221 return pairs(_items(inst, names, Nones), **kwds) 

222 

223 

224def enstr2(easting, northing, prec, *extras, **wide_dot): 

225 '''Return an MGRS/OSGR easting, northing string representations. 

226 

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. 

235 

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}}. 

238 

239 @raise ValueError: Invalid B{C{easting}}, B{C{northing}} or B{C{prec}}. 

240 

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 

257 

258if enstr2.__doc__: # PYCHOK expected 

259 enstr2.__doc__ %= (_EN_WIDE,) 

260 

261 

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 

272 

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 

279 

280 

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. 

283 

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. 

296 

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)) 

304 

305 

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 

315 

316 

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 

326 

327 

328def fstrzs(efstr, ap1z=False): 

329 '''Strip trailing zero decimals from a C{float} string. 

330 

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}). 

334 

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:]) 

347 

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:]) 

359 

360 return efstr 

361 

362 

363def hstr(height, prec=2, fmt=Fmt.h, ints=False, m=NN): 

364 '''Return a string for the height value. 

365 

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 

376 

377 

378def instr(inst, *args, **kwds): 

379 '''Return the string representation of an instantiation. 

380 

381 @arg inst: The instance (any C{type}). 

382 @arg args: Optional positional arguments. 

383 @kwarg kwds: Optional keyword arguments. 

384 

385 @return: Representation (C{str}). 

386 ''' 

387 return unstr(_MODS.named.classname(inst), *args, **kwds) 

388 

389 

390def lrstrip(txt, lrpairs=_LR_PAIRS): 

391 '''Left- I{and} right-strip parentheses, brackets, etc. from a string. 

392 

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). 

396 

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 

407 

408 

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}. 

411 

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}). 

419 

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 

433 

434 

435def _pct(fmt): 

436 '''(INTERNAL) Prefix C{%} if needed. 

437 ''' 

438 return fmt if _PERCENT_ in fmt else NN(_PERCENT_, fmt) 

439 

440 

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}. 

443 

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}). 

450 

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 () 

454 

455 

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) 

466 

467 

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) 

475 

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 

484 

485 else: 

486 raise _ValueError(fmt=fmt, txt=_not_(repr(_Fspec_))) 

487 

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 

502 

503 

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}. 

506 

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}). 

513 

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 () 

517 

518 

519def unstr(where, *args, **kwds): 

520 '''Return the string representation of an invokation. 

521 

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}. 

526 

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)) 

536 

537 

538def _0wd(*w_i): # in .osgr, .wgrs 

539 '''(INTERNAL) Int formatter'. 

540 ''' 

541 return '%0*d' % w_i 

542 

543 

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 

551 

552 

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 

557 

558 

559def _xattrs(insto, other, *attrs): # see .errors._xattr 

560 '''(INTERNAL) Copy attribute values from B{C{other}} to B{C{insto}}. 

561 

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). 

565 

566 @return: Object B{C{insto}}, updated. 

567 

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) 

579 

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 

586 

587 

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 

596 

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.