Source code for rics.strings

"""Utility functions that act on or produce strings."""


[docs] def format_bytes(n: int, *, binary: bool = True, long: bool = False, decimals: int = 2) -> str: """Format bytes as a string. Args: n: Number of bytes. Must be positive. binary: Output `binary <https://en.wikipedia.org/wiki/Binary_prefix>`_ prefixes if ``True``, use `metric (SI) <https://en.wikipedia.org/wiki/Metric_prefix>`_ prefixes otherwise. long: Output out full unit and prefix if ``True``, use abbreviated versions otherwise. decimals: Number of decimals to include. Ignored for when `n < base`. Returns: Formatted number of bytes. Examples: **Formatting on prefix bounds** The jump as made at `base / 2`, where `base` is one of 1024 and 1000 (when ``binary=False``). >>> format_bytes(512 * 1024) '512.00 KiB' >>> format_bytes(512 * 1024 + 1) '0.50 MiB' This rule does *not* apply when `n <= base`. >>> format_bytes(1024, long=True) 1024 bytes >>> format_bytes(1024 + 1) '1.00 KiB' **Output flags** >>> format_bytes(20190511, binary=False, long=False) '20.19 MB' >>> format_bytes(20190511, binary=False, long=True) '20.19 megabytes' >>> format_bytes(20190511, binary=True, long=False) '19.26 MiB' >>> format_bytes(20190511, binary=True, long=True) '19.26 mebibytes' **Large outputs** Metric and binary have different upper limits. >>> format_bytes(21**21, binary=True) '2416.44 YiB' >>> format_bytes(21**21, binary=True, long=True) '2416.44 yobibytes' >>> format_bytes(21**21, binary=False) '5.84 RB' >>> format_bytes(21**21, binary=False, long=True) '5.84 ronnabytes' If you ever see output like this, please let me know so that I can brag that someone important is using my little library. """ base = 1024 if binary else 1000 if n <= base: return f"{n} {'bytes' if long else 'B'}" x: float = n * 2.0 n_divisions = -1 while x > base: x /= base n_divisions += 1 if binary: # https://en.wikipedia.org/wiki/Binary_prefix if long: prefixes = ["kibi", "mebi", "gibi", "tebi", "pebi", "exbi", "zebi", "yobi"] else: prefixes = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] else: # noqa: PLR5501 # BUG: this rule doesn't preserve comments: https://github.com/astral-sh/ruff/issues/9790 # https://en.wikipedia.org/wiki/Metric_prefix if long: prefixes = ["kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta", "ronna", "quetta"] else: prefixes = ["k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"] try: prefix = prefixes[n_divisions] except IndexError: prefix = prefixes[-1] x = n / base ** len(prefixes) prefix += "bytes" if long else "B" return f"{x / 2:.{decimals}f} {prefix}"
[docs] def format_perf_counter(start: float, *, end: float | None = None) -> str: """Format performance counter output. This function formats performance counter output based on the time elapsed. This is a thin wrapper around the :func:`~rics.strings.format_seconds` function. Args: start: Start time. end: End time. Retrieved using :py:func:`time.perf_counter` if ``None``. Returns: A formatted performance counter time. Examples: Basic usage. >>> import time >>> start = time.perf_counter() >>> time.sleep(1219.0) # doctest: +SKIP >>> format_perf_counter(start) # doctest: +SKIP '20m 19s' With no `end` argument given, the current time is retrieved using :py:func:`time.perf_counter`. """ from time import perf_counter end = perf_counter() if end is None else end return format_seconds(end - start)
[docs] def format_seconds(t: float, *, allow_negative: bool = False) -> str: """Format performance counter output. Args: t: Time in seconds. allow_negative: If ``True``, format negative `t` with a leading minus sign. Returns: A formatted performance counter time. Examples: Basic usage. >>> format_seconds(0.0000154) '15μs' >>> format_seconds(0.154) '154ms' >>> format_seconds(31.39) '31.4s' Clock units are used for `t > 60` seconds. >>> format_seconds(59.99) '60.0s' >>> format_seconds(60.00) '60.0s' >>> format_seconds(60.01) '1m' >>> format_seconds(309613.49) '3d 14h 0m 13s' Raises: ValueError: If ``t < 0`` and ``allow_negative=False`` (the default). """ if t < 0: if not allow_negative: allow_negative = True raise ValueError(f"Refuse to format {t=} < 0; to allow, set {allow_negative=}") return f"-{format_seconds(abs(t))}" long_limit: float = 60.0 return _format_seconds(t) if t <= long_limit else _format_minutes(t)
def _format_minutes(t: float) -> str: days, seconds = divmod(round(t), 86400) hours, seconds = divmod(seconds, 3600) minutes, seconds = divmod(seconds, 60) parts = (days, hours, minutes, seconds) nonzero = tuple(p > 0 for p in parts) start, stop = nonzero.index(True), len(nonzero) - nonzero[::-1].index(True) return " ".join(f"{parts[i]}{'dhms'[i]}" for i in range(start, stop)) def _format_seconds(t: float) -> str: single_decimal_limit: float = 1.0 if t >= single_decimal_limit: return f"{t:.1f}s" double_decimal_limit: float = 0.5 if t > double_decimal_limit: return f"{t:.2f}s" if t > 10**-3: return f"{t * 10**3:.0f}ms" if t > 10**-6: # 1 μs return f"{t * 10**6:.0f}μs" if t > 10**-9: return f"{t * 10**9:.0f}ns" return f"{t:.3g}s"