Coverage for pygeodesy/streprs.py: 95%

1

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

3

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

5'''

6

7 from pygeodesy.basics import isint, islistuple, isscalar, isstr, itemsorted, \

8 _zip, _0_0, typename

9# from pygeodesy.constants import _0_0

10 from pygeodesy.errors import _or, _IsnotError, _TypeError, _ValueError, \

11 _xkwds_get, _xkwds_item2

12# from pygeodesy.internals import typename # from .basics

13 from 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_

17 from pygeodesy.interns import _convergence_, _distant_, _e_, _exceeds_, \

18 _EQUALSPACED_, _f_, _F_, _g_, _limit_, \

19 _no_, _tolerance_ # PYCHOK used!

20 from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS

21

22 from math import fabs, log10 as _log10

23

24 __all__ = _ALL_LAZY.streprs

25 __version__ = '25.11.24'

26

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!

35

36

37 class _Fmt(str):

38 '''(INTERNAL) Callable formatting.

39 '''

40 name = NN

41

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

53

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

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

56# '''

57# return str.__mod__(self, arg)

58

59

60 class Fstr(str):

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

62 '''

63 name = NN

64

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

72

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

74 '''Regular C{%} operator.

75

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

79

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

81

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

87

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

97

98 if not isscalar(arg):

99 raise _TypeError(_error(arg))

100 return self(arg, prec=prec) # Fstr.__call__(self, arg, prec=prec)

101

102

103 class _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)

110

111

112 class 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

149

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

155

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

161

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)

168

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)

180

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

185

186 Fmt = Fmt() # PYCHOK singleton

187

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

194

195 del _convergence_, _distant_, _e_, _eps_, _exceeds_, _EQUALSPACED_,\

196 _f_, _F_, _g_, _limit_, _PAREN_g, _RESIDUAL_

197

198

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

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

201

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

205

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

207

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

217

218

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

222

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.

227

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

236

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

238 return Nones, kwds

239

240 Nones, kwds = _Nones_kwds(**Nones_True__pairs_kwds)

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

242

243

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

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

246

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.

255

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

258

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

260

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

277

278 if enstr2.__doc__: # PYCHOK expected

279 enstr2.__doc__ %= (_EN_WIDE,)

280

281

282 def _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

292

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

299

300

301 def 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.

303

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

319

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 t = next(_streprs(prec, (floats,), fmt, ints, force, strepr))

325 else:

326 t = _streprs(prec, floats, fmt, ints, force, strepr)

327 t = sep.join(t) if sep else tuple(t)

328 return t

329

330

331 def _fstrENH2(inst, prec, m, fmt=Fmt.F): # in .css, .lcc, .utmupsBase

332 # (INTERNAL) For C{Css.} and C{Lcc.} C{toRepr} and C{toStr} and C{UtmUpsBase._toStr}.

333 t = inst.easting, inst.northing

334 t = tuple(_streprs(prec, t, fmt, False, True, None))

335 T = _E_, _N_

336 if m is not None and fabs(inst.height): # fabs(self.height) > EPS

337 t += hstr(inst.height, prec=-2, m=m),

338 T += _H_,

339 return t, T

340

341

342 def _fstrLL0(inst, prec, toRepr): # in .azimuthal, .css

343 # (INTERNAL) For C{_AlbersBase.}, C{_AzimuthalBase.} and C{CassiniSoldner.}

344 t = tuple(_streprs(prec, inst.latlon0, Fmt.F, False, True, None))

345 if toRepr:

346 n = inst.name

347 if n:

348 t += Fmt.EQUAL(_name_, repr(n)),

349 t = Fmt.PAREN(inst.classname, _COMMASPACE_.join(t))

350 return t

351

352

353 def fstrzs(efstr, ap1z=False):

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

355

356 @arg efstr: Float with or without exponent (C{str}).

357 @kwarg ap1z: Append the decimal point and one zero decimal

358 if the B{C{efstr}} is all digits (C{bool}).

359

360 @return: Float (C{str}).

361 '''

362 s = efstr.find(_DOT_)

363 if s >= 0:

364 e = efstr.rfind(Fmt.e)

365 if e < 0:

366 e = efstr.rfind(Fmt.E)

367 if e < 0:

368 e = len(efstr)

369 s += 2 # keep 1st _DOT_ + _0_

370 if s < e and efstr[e-1] == _0_:

371 efstr = NN(efstr[:s], efstr[s: e].rstrip(_0_), efstr[e:])

372

373 elif ap1z:

374 # %.G and %.g formats may drop the decimal

375 # point and all trailing zeros, ...

376 if efstr.isdigit():

377 efstr += _DOT_ + _0_ # ... append or ...

378 else: # ... insert one dot and zero

379 e = efstr.rfind(Fmt.e)

380 if e < 0:

381 e = efstr.rfind(Fmt.E)

382 if e > 0:

383 efstr = NN(efstr[:e], _DOT_, _0_, efstr[e:])

384

385 return efstr

386

387

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

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

390

391 @arg height: Height value (C{float}).

392 @kwarg prec: The C{float} precision, number of decimal digits (0..9).

393 Trailing zero decimals are stripped if B{C{prec}} is

394 positive, but kept for negative B{C{prec}} values.

395 @kwarg fmt: Optional C{float} format (C{letter}).

396 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).

397 @kwarg m: Optional unit of the height (C{str}).

398 '''

399 h = next(_streprs(prec, (height,), fmt, ints, True, None))

400 return NN(h, str(m)) if m else h

401

402

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

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

405

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

407 @arg args: Optional positional arguments.

408 @kwarg kwds: Optional keyword arguments.

409

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

411 '''

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

413

414

415 def lrstrip(txt, lrpairs=_LR_PAIRS):

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

417

418 @arg txt: String to be stripped (C{str}).

419 @kwarg lrpairs: Parentheses, etc. to remove (C{dict} of one or several

420 C{(Left, Right)} pairs).

421

422 @return: Stripped B{C{txt}} (C{str}).

423 '''

424 _e, _s, _n = str.endswith, str.startswith, len

425 while _n(txt) > 2:

426 for L, R in lrpairs.items():

427 if _e(txt, R) and _s(txt, L):

428 txt = txt[_n(L):-_n(R)]

429 break # restart

430 else:

431 return txt

432

433

434 def pairs(items, prec=6, fmt=Fmt.F, ints=False, sep=_EQUAL_):

435 '''Convert items to I{name=value} strings, with C{float}s handled like L{fstr}.

436

437 @arg items: Name-value pairs (C{dict} or 2-{tuple}s of any C{type}s).

438 @kwarg prec: The C{float} precision, number of decimal digits (0..9).

439 Trailing zero decimals are stripped if B{C{prec}} is

440 positive, but kept for negative B{C{prec}} values.

441 @kwarg fmt: Optional C{float} format (C{letter}).

442 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).

443 @kwarg sep: Separator joining I{names} and I{values} (C{str}).

444

445 @return: A C{tuple(B{sep}.join(t) for t in B{items}))} of C{str}s.

446 '''

447 try:

448 if isinstance(items, dict):

449 items = itemsorted(items)

450 elif not islistuple(items):

451 items = tuple(items)

452 # can't unzip empty items tuple, list, etc.

453 n, v = _zip(*items) if items else ((), ()) # strict=True

454 except (TypeError, ValueError):

455 raise _IsnotError(dict, '2-tuples', items=items)

456 v = _streprs(prec, v, fmt, ints, False, repr)

457 return tuple(sep.join(t) for t in _zip(map(str, n), v)) # strict=True

458

459

460 def _pct(fmt):

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

462 '''

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

464

465

466 def reprs(objs, prec=6, fmt=Fmt.F, ints=False):

467 '''Convert objects to C{repr} strings, with C{float}s handled like L{fstr}.

468

469 @arg objs: List, sequence, tuple, etc. (any C{type}s).

470 @kwarg prec: The C{float} precision, number of decimal digits (0..9).

471 Trailing zero decimals are stripped if B{C{prec}} is

472 positive, but kept for negative B{C{prec}} values.

473 @kwarg fmt: Optional C{float} format (C{letter}).

474 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).

475

476 @return: A C{tuple(map(fstr|repr, objs))} of C{str}s.

477 '''

478 return tuple(_streprs(prec, objs, fmt, ints, False, repr)) if objs else ()

479

480

481 def _resolution10(resolution, Error=ValueError): # in .mgrs, .osgr

482 '''(INTERNAL) Validate C{resolution} in C{meter}.

483 '''

484 try:

485 r = int(_log10(resolution))

486 if _EN_WIDE < r or r < -_EN_PREC:

487 raise ValueError

488 except (ValueError, TypeError):

489 raise Error(resolution=resolution)

490 return _MODS.units.Meter(resolution=10**r)

491

492

493 def _streprs(prec, objs, fmt, ints, force, strepr):

494 '''(INTERNAL) Helper for C{fstr}, C{pairs}, C{reprs} and C{strs}

495 '''

496 # <https://docs.Python.org/3/library/stdtypes.html#printf-style-string-formatting>

497 if fmt in _FfEeGg:

498 fGg = fmt in _Gg

499 fmt = NN(_PERCENT_, _DOT_, abs(prec), fmt)

500

501 elif fmt.startswith(_PERCENT_):

502 fGg = False

503 try: # to make sure fmt is valid

504 f = fmt.replace(_DOTSTAR_, Fmt.DOT(abs(prec)))

505 _ = f % (_0_0,)

506 except (TypeError, ValueError):

507 raise _ValueError(fmt=fmt, txt_not_=repr(_DOTSTAR_))

508 fmt = f

509

510 else:

511 raise _ValueError(fmt=fmt, txt_not_=repr(_Fspec_))

512

513 for i, o in enumerate(objs):

514 if force or isinstance(o, float):

515 t = fmt % (float(o),)

516 if ints and t.rstrip(_0to9_ if isint(o, both=True) else

517 _0_).endswith(_DOT_):

518 t = t.split(_DOT_)[0]

519 elif prec > 1:

520 t = fstrzs(t, ap1z=fGg)

521 elif strepr:

522 t = strepr(o)

523 else:

524 t = Fmt.PARENSPACED(Fmt.SQUARE(objs=i), o)

525 raise TypeError(_SPACE_(t, _not_scalar_))

526 yield t

527

528

529 def strs(objs, prec=6, fmt=Fmt.F, ints=False):

530 '''Convert objects to C{str} strings, with C{float}s handled like L{fstr}.

531

532 @arg objs: List, sequence, tuple, etc. (any C{type}s).

533 @kwarg prec: The C{float} precision, number of decimal digits (0..9).

534 Trailing zero decimals are stripped if B{C{prec}} is

535 positive, but kept for negative B{C{prec}} values.

536 @kwarg fmt: Optional C{float} format (C{letter}).

537 @kwarg ints: Optionally, remove the decimal dot for C{int} values (C{bool}).

538

539 @return: A C{tuple(map(fstr|str, objs))} of C{str}s.

540 '''

541 return tuple(_streprs(prec, objs, fmt, ints, False, str)) if objs else ()

542

543

544 def unstr(where, *args, **kwds_):

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

546

547 @arg where: Class, function, method (C{type}) or name (C{str}).

548 @arg args: Optional positional arguments.

549 @kwarg kwds_: Optional keyword arguments, except C{B{_Cdot}=None},

550 C{B{_ELLIPSIS}=False}, C{B{_fmt}=Fmt.g} and C{B{_prec}=6}.

551

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

553 '''

554 def _C_e_fmt_prec_kwds(_Cdot=None, _ELLIPSIS=0, _fmt=Fmt.g, _prec=6, **kwds):

555 return _Cdot, _ELLIPSIS, dict(fmt=_fmt, prec=_prec), kwds

556

557 C, e, fmt_prec, kwds = _C_e_fmt_prec_kwds(**kwds_)

558 if e and len(args) > (e + 1):

559 t = reprs(args[:e], **fmt_prec)

560 t += _ELLIPSIS_,

561 t += reprs(args[-1:], **fmt_prec)

562 else:

563 t = reprs(args, **fmt_prec) if args else ()

564 if kwds:

565 t += pairs(itemsorted(kwds), **fmt_prec)

566 n = where if isstr(where) else typename(where) # _NN_

567 if C and hasattr(C, n):

568 try: # bound method of class C?

569 where = where.__func__

570 except AttributeError:

571 pass # method of C?

572 if getattr(C, n, None) is where:

573 n = _DOT_(typename(C), n)

574 return Fmt.PAREN(n, _COMMASPACE_.join(t))

575

576

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

578 '''(INTERNAL) Int formatter'.

579 '''

580 return '%0*d' % w_i

581

582

583 def _0wdot(w, f, dot=0):

584 '''(INTERNAL) Int and Float formatter'.

585 '''

586 s = _0wd(w, int(f))

587 if dot:

588 s = _DOT_(s[:dot], s[dot:])

589 return s

590

591

592 def _0wpF(*w_p_f): # in .dms, .osgr

593 '''(INTERNAL) Float deg, min, sec formatter'.

594 '''

595 return '%0*.*f' % w_p_f # XXX was F

596

597

598 def _xzipairs(names, values, sep=_COMMASPACE_, fmt=NN, pair_fmt=Fmt.COLON):

599 '''(INTERNAL) Zip C{names} and C{values} into a C{str}, joined and bracketed.

600 '''

601 try:

602 t = sep.join(pair_fmt(*t) for t in _zip(names, values)) # strict=True

603 except Exception as x:

604 raise _ValueError(names=names, values=values, cause=x)

605 return (fmt % (t,)) if fmt else t # enc

606

607

608# **) MIT License

609#

610# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

611#

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

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

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

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

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

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

618#

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

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

621#

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

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

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

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

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

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

628# OTHER DEALINGS IN THE SOFTWARE.