Source code for pint.facets.formatting.objects

"""
    pint.facets.formatting.objects
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    :copyright: 2022 by Pint Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

import re
from typing import Any, Generic

from ...compat import babel_parse, ndarray, np
from ...formatting import (
    _pretty_fmt_exponent,
    extract_custom_flags,
    format_unit,
    ndarray_to_latex,
    remove_custom_flags,
    siunitx_format_unit,
    split_format,
)
from ...util import UnitsContainer, iterable

from ..plain import PlainQuantity, PlainUnit, MagnitudeT


[docs]class FormattingQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): _exp_pattern = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)") def __format__(self, spec: str) -> str: if self._REGISTRY.fmt_locale is not None: return self.format_babel(spec) mspec, uspec = split_format( spec, self.default_format, self._REGISTRY.separate_format_defaults ) # If Compact is selected, do it at the beginning if "#" in spec: # TODO: don't replace '#' mspec = mspec.replace("#", "") uspec = uspec.replace("#", "") obj = self.to_compact() else: obj = self if "L" in uspec: allf = plain_allf = r"{}\ {}" elif "H" in uspec: allf = plain_allf = "{} {}" if iterable(obj.magnitude): # Use HTML table instead of plain text template for array-likes allf = ( "<table><tbody>" "<tr><th>Magnitude</th>" "<td style='text-align:left;'>{}</td></tr>" "<tr><th>Units</th><td style='text-align:left;'>{}</td></tr>" "</tbody></table>" ) else: allf = plain_allf = "{} {}" if "Lx" in uspec: # the LaTeX siunitx code # TODO: add support for extracting options opts = "" ustr = siunitx_format_unit(obj.units._units, obj._REGISTRY) allf = r"\SI[%s]{{{}}}{{{}}}" % opts else: # Hand off to unit formatting # TODO: only use `uspec` after completing the deprecation cycle ustr = format(obj.units, mspec + uspec) # mspec = remove_custom_flags(spec) if "H" in uspec: # HTML formatting if hasattr(obj.magnitude, "_repr_html_"): # If magnitude has an HTML repr, nest it within Pint's mstr = obj.magnitude._repr_html_() else: if isinstance(self.magnitude, ndarray): # Use custom ndarray text formatting with monospace font formatter = f"{{:{mspec}}}" # Need to override for scalars, which are detected as iterable, # and don't respond to printoptions. if self.magnitude.ndim == 0: allf = plain_allf = "{} {}" mstr = formatter.format(obj.magnitude) else: with np.printoptions( formatter={"float_kind": formatter.format} ): mstr = ( "<pre>" + format(obj.magnitude).replace("\n", "<br>") + "</pre>" ) elif not iterable(obj.magnitude): # Use plain text for scalars mstr = format(obj.magnitude, mspec) else: # Use monospace font for other array-likes mstr = ( "<pre>" + format(obj.magnitude, mspec).replace("\n", "<br>") + "</pre>" ) elif isinstance(self.magnitude, ndarray): if "L" in uspec: # Use ndarray LaTeX special formatting mstr = ndarray_to_latex(obj.magnitude, mspec) else: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions formatter = f"{{:{mspec}}}" if obj.magnitude.ndim == 0: mstr = formatter.format(obj.magnitude) else: with np.printoptions(formatter={"float_kind": formatter.format}): mstr = format(obj.magnitude).replace("\n", "") else: mstr = format(obj.magnitude, mspec).replace("\n", "") if "L" in uspec and "Lx" not in uspec: mstr = self._exp_pattern.sub(r"\1\\times 10^{\2\3}", mstr) elif "H" in uspec or "P" in uspec: m = self._exp_pattern.match(mstr) _exp_formatter = ( _pretty_fmt_exponent if "P" in uspec else lambda s: f"<sup>{s}</sup>" ) if m: exp = int(m.group(2) + m.group(3)) mstr = self._exp_pattern.sub(r"\1×10" + _exp_formatter(exp), mstr) if allf == plain_allf and ustr.startswith("1 /"): # Write e.g. "3 / s" instead of "3 1 / s" ustr = ustr[2:] return allf.format(mstr, ustr).strip() def _repr_pretty_(self, p, cycle): if cycle: super()._repr_pretty_(p, cycle) else: p.pretty(self.magnitude) p.text(" ") p.pretty(self.units) def format_babel(self, spec: str = "", **kwspec: Any) -> str: spec = spec or self.default_format # standard cases if "#" in spec: spec = spec.replace("#", "") obj = self.to_compact() else: obj = self kwspec = kwspec.copy() if "length" in kwspec: kwspec["babel_length"] = kwspec.pop("length") loc = kwspec.get("locale", self._REGISTRY.fmt_locale) if loc is None: raise ValueError("Provide a `locale` value to localize translation.") kwspec["locale"] = babel_parse(loc) kwspec["babel_plural_form"] = kwspec["locale"].plural_form(obj.magnitude) return "{} {}".format( format(obj.magnitude, remove_custom_flags(spec)), obj.units.format_babel(spec, **kwspec), ).replace("\n", "") def __str__(self) -> str: if self._REGISTRY.fmt_locale is not None: return self.format_babel() return format(self)
[docs]class FormattingUnit(PlainUnit): def __str__(self): return format(self) def __format__(self, spec) -> str: _, uspec = split_format( spec, self.default_format, self._REGISTRY.separate_format_defaults ) if "~" in uspec: if not self._units: return "" units = UnitsContainer( { self._REGISTRY._get_symbol(key): value for key, value in self._units.items() } ) uspec = uspec.replace("~", "") else: units = self._units return format_unit(units, uspec, registry=self._REGISTRY) def format_babel(self, spec="", locale=None, **kwspec: Any) -> str: spec = spec or extract_custom_flags(self.default_format) if "~" in spec: if self.dimensionless: return "" units = UnitsContainer( { self._REGISTRY._get_symbol(key): value for key, value in self._units.items() } ) spec = spec.replace("~", "") else: units = self._units locale = self._REGISTRY.fmt_locale if locale is None else locale if locale is None: raise ValueError("Provide a `locale` value to localize translation.") else: kwspec["locale"] = babel_parse(locale) return units.format_babel(spec, registry=self._REGISTRY, **kwspec)