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)