1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
"""
pint.delegates.formatter._spec_helpers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Convenient functions to deal with format specifications.
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
import functools
import re
import warnings
from collections.abc import Callable
from typing import Any
FORMATTER = Callable[
[
Any,
],
str,
]
# Extract just the type from the specification mini-language: see
# http://docs.python.org/2/library/string.html#format-specification-mini-language
# We also add uS for uncertainties.
_BASIC_TYPES = frozenset("bcdeEfFgGnosxX%uS")
REGISTERED_FORMATTERS: dict[str, Any] = {}
def parse_spec(spec: str) -> str:
"""Parse and return spec.
If an unknown item is found, raise a ValueError.
This function still needs work:
- what happens if two distinct values are found?
"""
result = ""
for ch in reversed(spec):
if ch == "~" or ch in _BASIC_TYPES:
continue
elif ch in list(REGISTERED_FORMATTERS.keys()) + ["~"]:
if result:
raise ValueError("expected ':' after format specifier")
else:
result = ch
elif ch.isalpha():
raise ValueError("Unknown conversion specified " + ch)
else:
break
return result
def extract_custom_flags(spec: str) -> str:
"""Return custom flags present in a format specification
(i.e those not part of Python's formatting mini language)
"""
if not spec:
return ""
# sort by length, with longer items first
known_flags = sorted(REGISTERED_FORMATTERS.keys(), key=len, reverse=True)
flag_re = re.compile("(" + "|".join(known_flags + ["~"]) + ")")
custom_flags = flag_re.findall(spec)
return "".join(custom_flags)
def remove_custom_flags(spec: str) -> str:
"""Remove custom flags present in a format specification
(i.e those not part of Python's formatting mini language)
"""
for flag in sorted(REGISTERED_FORMATTERS.keys(), key=len, reverse=True) + ["~"]:
if flag:
spec = spec.replace(flag, "")
return spec
##########
# This weird way of defining split format
# is the only reasonable way I foudn to use
# lru_cache in a function that might emit warning
# and do it every time.
# TODO: simplify it when there are no warnings.
@functools.lru_cache
def _split_format(
spec: str, default: str, separate_format_defaults: bool = True
) -> tuple[str, str, list[str]]:
"""Split format specification into magnitude and unit format."""
mspec = remove_custom_flags(spec)
uspec = extract_custom_flags(spec)
default_mspec = remove_custom_flags(default)
default_uspec = extract_custom_flags(default)
warns = []
if separate_format_defaults in (False, None):
# should we warn always or only if there was no explicit choice?
# Given that we want to eventually remove the flag again, I'd say yes?
if spec and separate_format_defaults is None:
if not uspec and default_uspec:
warns.append(
"The given format spec does not contain a unit formatter."
" Falling back to the builtin defaults, but in the future"
" the unit formatter specified in the `default_format`"
" attribute will be used instead."
)
if not mspec and default_mspec:
warns.append(
"The given format spec does not contain a magnitude formatter."
" Falling back to the builtin defaults, but in the future"
" the magnitude formatter specified in the `default_format`"
" attribute will be used instead."
)
elif not spec:
mspec, uspec = default_mspec, default_uspec
else:
mspec = mspec or default_mspec
uspec = uspec or default_uspec
return mspec, uspec, warns
def split_format(
spec: str, default: str, separate_format_defaults: bool = True
) -> tuple[str, str]:
"""Split format specification into magnitude and unit format."""
mspec, uspec, warns = _split_format(spec, default, separate_format_defaults)
for warn_msg in warns:
warnings.warn(warn_msg, DeprecationWarning)
return mspec, uspec
|