Source code for click_extra.humanize
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Human-readable rendering of machine values.
Formatters that turn raw numbers into the compact strings shown in terminal
output, tables and reports. The reverse direction, parsing a human-written
value back into a machine type, lives in {mod}`click_extra.types` (see
{class}`~click_extra.types.Duration`).
"""
from __future__ import annotations
from datetime import timedelta
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Literal
_UNIT_SYSTEMS: dict[str, tuple[int, tuple[str, ...]]] = {
"iec": (1024, ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")),
"si": (1000, ("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")),
"jedec": (1024, ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")),
}
"""Byte-unit systems, mapping a name to its base and unit symbols.
- `iec`: binary powers of `1024` with the unambiguous IEC symbols (`KiB`, `MiB`).
- `si`: decimal powers of `1000` with the SI symbols (`kB`, `MB`).
- `jedec`: binary powers of `1024` with the customary symbols (`KB`, `MB`): the
widespread convention (Windows, many CLIs), though `KB` for 1024 bytes is
imprecise.
"""
[docs]
def format_size(
size: float, *, units: Literal["iec", "si", "jedec"] = "iec", precision: int = 1
) -> str:
"""Render a byte count as a compact, human-readable string.
:param size: The number of bytes. A negative value keeps a leading `-`.
:param units: The unit system to render in, one of {data}`_UNIT_SYSTEMS`:
`iec` (the default) for binary powers with the unambiguous `KiB`/`MiB`
symbols, `si` for decimal powers with `kB`/`MB`, or `jedec` for binary
powers with the customary but imprecise `KB`/`MB`.
:param precision: Number of fractional digits for every unit above bytes. A
byte count is always rendered as a whole number.
:return: The size followed by a space and its unit, like `1.5 KiB`. The
integer part is grouped with thousands separators.
:raises ValueError: If *units* is not a known unit system.
"""
try:
base, symbols = _UNIT_SYSTEMS[units]
except KeyError:
msg = f"Unknown unit system {units!r}; pick one of {sorted(_UNIT_SYSTEMS)}."
raise ValueError(msg) from None
sign = "-" if size < 0 else ""
amount = float(abs(size))
index = 0
last = len(symbols) - 1
while amount >= base and index < last:
amount /= base
index += 1
unit = symbols[index]
# Bytes have no fractional part; every larger unit honors `precision`.
if index == 0:
return f"{sign}{round(amount):,} {unit}"
return f"{sign}{amount:,.{precision}f} {unit}"
[docs]
def format_duration(duration: float | timedelta) -> str:
"""Render an elapsed duration compactly: `2.3s`, `1:05`, then `1:02:03`.
Below a minute the duration reads as one-decimal seconds (`2.3s`). From a
minute up it switches to a clock layout, growing an hours field only once it
reaches an hour: `1:05` under an hour, `1:02:03` at or above.
The reverse direction, parsing a human-written duration back into a
{class}`~datetime.timedelta`, lives in {mod}`click_extra.types` (see
{class}`~click_extra.types.Duration`).
:param duration: The elapsed time, as a number of seconds or a
{class}`~datetime.timedelta`.
:return: The compact rendering described above.
"""
if isinstance(duration, timedelta):
duration = duration.total_seconds()
if duration < 60:
return f"{duration:.1f}s"
minutes, secs = divmod(int(duration), 60)
hours, minutes = divmod(minutes, 60)
if hours:
return f"{hours}:{minutes:02d}:{secs:02d}"
return f"{minutes}:{secs:02d}"