Source code for framed_text.labeled_data

import textwrap
from datetime import datetime
from pathlib import Path
from typing import Literal

from framed_text.utils import (
    _format_col_text,
    _format_big_number,
    _human_readable_bytes,
    _remove_ansi,
    _truncate_text_middle,
    _validate_attrs,
    _validate_color,
)


[docs] class LabeledData: """ Class for creating a string with a label and different types of data. Each item can be configured to include colored text. """ @staticmethod def _format_text(label: str, value: str, suffix: str, quotes: bool, colon_match: bool = True, no_colon: bool = False, val_color: str | tuple[int, int, int] | None = None, label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ Compile each part of the LabeledData string into a single string :param label: Label :param value: Value :param suffix: Suffix :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_color: Color of the value :param label_color: Color of the label :param suffix_color: Color of the suffix :param val_attrs: Attributes to apply to the value :param label_attrs: Attributes to apply to the label :param suffix_attrs: Attributes to apply to the suffix :return: LabeledData text string """ # Label _label: str = _format_col_text(text=label, color=label_color, attrs=label_attrs) # Colon formatting if needed if colon_match and not no_colon: _col: str = _format_col_text(text=':', color=label_color) elif not no_colon: _col: str = ':' else: _col: str = '' # Value if quotes: _value: str = _format_col_text(text=f"'{value}'", color=val_color, attrs=val_attrs) else: _value: str = _format_col_text(text=value, color=val_color, attrs=val_attrs) # Suffix _suffix: str = _format_col_text(text=suffix, color=suffix_color, attrs=suffix_attrs) if suffix: return f"{_label}{_col} {_value} {_suffix}" else: return f"{_label}{_col} {_value}" @staticmethod def _format_bytes(label: str, value: int, bytes_hr: float, unit: str, suffix: str, quotes: bool, colon_match: bool = True, unit_match: bool = True, no_colon: bool = False, show_unit: bool = True, val_color: str | tuple[int, int, int] | None = None, label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, unit_color: str | tuple[int, int, int] | None = None, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None, unit_attrs: list[str] | None = None) -> str: """ Compile each part of the LabeledData string into a single string :param label: Label :param value: Value :param bytes_hr: Bytes value in a human-readable format. :param unit: Unit :param suffix: Suffix :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label :param unit_match: If true, the unit will match the style of the value :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param show_unit: If true, the unit will be shown :param val_color: Color of the value :param label_color: Color of the label :param suffix_color: Color of the suffix :param unit_color: Color of the unit :param val_attrs: Attributes to apply to the value :param label_attrs: Attributes to apply to the label :param suffix_attrs: Attributes to apply to the suffix :param unit_attrs: Attributes to apply to the unit :return: LabeledData text string """ # Label _label: str = _format_col_text(text=label, color=label_color, attrs=label_attrs) # Colon formatting if needed if colon_match and not no_colon: _col: str = _format_col_text(text=':', color=label_color) elif not no_colon: _col: str = ':' else: _col: str = '' # Unit & Value _unit: str = '' _unit_color: str | tuple[int, int, int] | None = None _unit_attrs: list[str] | None = None # Override unit_match if a unit color/attr is passed if unit_match: _unit_color = val_color if not unit_color else unit_color _unit_attrs = val_attrs if not unit_attrs else unit_attrs else: _unit_color = unit_color _unit_attrs = unit_attrs if show_unit: # If the value is large (Over 32 bits), format it to E-Notation if bytes_hr and abs(bytes_hr) > 0xffffffff: _value: str = _format_big_number(num=bytes_hr) elif bytes_hr: _value: str = f"{bytes_hr:,.2f}" elif abs(value) > 0xffffffff: _value: str = _format_big_number(num=value) else: _value: str = str(value) _unit = _format_col_text(text=unit, color=_unit_color, attrs=_unit_attrs) else: _value: str = str(value) _value = _format_col_text(text=_value, color=val_color, attrs=val_attrs) # Suffix _suffix: str = _format_col_text(text=suffix, color=suffix_color, attrs=suffix_attrs) _val_unit: str = f"'{_value} {_unit}'" if quotes else f"{_value} {_unit}" if suffix: return f"{_label}{_col} {_val_unit} {_suffix}" else: return f"{_label}{_col} {_val_unit}" @staticmethod def _shorten_string(label: str, value: str, suffix: str, text: str, limit: int, quotes: bool, mode: Literal["char", "word", "middle"], val_color: str | tuple[int, int, int] | None = "cyan", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ For use with LabeledData.String. Internal only :param label: Label :param value: Value :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param mode: Mode passed from LabeledData.String. Same as LabeledData.String.cutoff_text :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :return: Shortened text """ # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) text_no_ansi: str = _remove_ansi(text=value) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' if len(text) <= limit: # Under/at limit return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis if len(label_no_ansi) <= limit // 2: label: str = str(label_no_ansi) else: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, value and suffix share limit if len(value) + len(suffix) <= text_limit: text_short: str = text_no_ansi else: text_short: str = '' # Calculate the ratio of text between the value and suffix _ratio: float = len(value) / (len(value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(value) > _limit_val: # Value match mode.lower(): case "char": # Shorten by char text_short = f"{text_no_ansi[:_limit_val]}…" case "word": # Shorten by word text_short = textwrap.shorten(text=text_no_ansi, width=_limit_val, placeholder="…") case "middle": # Shorten by middle characters text_short = _truncate_text_middle(text=text_no_ansi, limit=_limit_val) else: text_short = text_no_ansi if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi elif len(value) > text_limit: match mode.lower(): case "char": # Shorten by char text_short: str = f"{text_no_ansi[:text_limit]}…" case "word": # Shorten by word text_short: str = textwrap.shorten(text=text_no_ansi, width=text_limit, placeholder="…") case "middle": # Shorten by middle characters text_short: str = _truncate_text_middle(text=text_no_ansi, limit=text_limit) else: text_short: str = text_no_ansi # Format Text return LabeledData._format_text(label=label, value=text_short, suffix=suffix_short, quotes=quotes, colon_match=colon_match, no_colon=no_colon, val_color=val_color, label_color=label_color, suffix_color=suffix_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs ) @staticmethod def _shorten_number(label: str, value: int | float, suffix: str, text: str, limit: int, quotes: bool, val_color: str | tuple[int, int, int] | None = "yellow", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, leading_zeros: int | None = None, int_no_round: bool = False, round_to: int | None = None, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ For use with LabeledData.Number. Internal only :param label: Label :param value: Value :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param round_to: Number of decimal places to round to. Leave blank to not round. :param leading_zeros: Number of leading zeros to add to the number. Leave blank to not add leading zeros. :param int_no_round: If true, displays number as integer without rounding. Overrides ``round_to`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :return: Shortened text """ # If the value is large (Over 32 bits), format it to E-Notation # Will override rounding and leading zeros if abs(value) > 0xffffffff: _value: str = _format_big_number(num=value) else: # Rounding if int_no_round: # Display num as integer _value: str = f"{int(value):,}" elif round_to is not None and round_to > 0: # Round _value: str = f"{round(value, round_to):,}" elif round_to is not None and round_to == 0: # Round to Integer. Don't display trailing 0 _value: str = f"{round(value, round_to):,.0f}" elif round_to: # Round error raise ValueError("'round' must be an integer greater than 0") else: # No rounding _value: str = f"{value:,}" # Leading Zeros if leading_zeros and leading_zeros >= 0: _value = f"{_value:0{leading_zeros}d}" elif leading_zeros: # Leading zeros error raise ValueError("'leading_zeros' must be an integer greater than 0") # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' if len(text) <= limit: # Under/at limit return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis if len(label_no_ansi) <= limit // 2: label: str = str(label_no_ansi) else: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, value and suffix share limit if len(_value) + len(suffix) > text_limit: # Calculate the ratio of text between the value and suffix _ratio: float = len(_value) / (len(_value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi # Format Text return LabeledData._format_text(label=label, value=_value, suffix=suffix_short, quotes=quotes, colon_match=colon_match, no_colon=no_colon, val_color=val_color, label_color=label_color, suffix_color=suffix_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs, ) @staticmethod def _shorten_path(label: str, value: str, text: str, suffix: str, limit: int, quotes: bool, mode: Literal["char", "word", "middle"], val_color: str | tuple[int, int, int] | None = "cyan", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ For use with LabeledData.Path. Internal only :param label: Label :param value: Value :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param mode: Mode passed from LabeledData.Path. Same as LabeledData.Path.cutoff_text :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :return: Shortened text """ # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) text_no_ansi: str = _remove_ansi(text=value) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' if len(text) <= limit: return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis if len(label_no_ansi) > limit // 2: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) else: label: str = str(label_no_ansi) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, vlaue and suffix share limit if len(value) + len(suffix) <= text_limit: text_short: str = text_no_ansi else: text_short: str = '' # Calculate the ratio of text between the value and suffix _ratio: float = len(value) / (len(value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(value) <= _limit_val: text_short = text_no_ansi else: # Value match mode.lower(): case "char": # Cut char at end text_short: str = f"{text_no_ansi[:_limit_val]}…" case "word": # Cut path at end # Removing leading/trailing slash if text_no_ansi[-1] == "/": text_no_ansi = text_no_ansi[:-1] if text_no_ansi[0] == "/": text_no_ansi = text_no_ansi[1:] # Split path to count text_parts: list[str] = text_no_ansi.split("/") # Remove last part and slashes from limit _limit_val -= len(text_parts[-1]) _limit_val -= value.count('/') char_cnt: int = 0 part_at_limit: int = -1 # Find which part exceeds limit for i, part in enumerate(text_parts): char_cnt += len(part) if char_cnt > _limit_val: part_at_limit = i break if part_at_limit != -1: # Cut of part, replace with ellipsis part_end: str = text_parts[-1] text_parts = text_parts[:part_at_limit] text_parts.insert(part_at_limit, "…") text_short: str = f"/{'/'.join(text_parts)}/{part_end}" else: # No part exceeds limit text_short: str = text_no_ansi case "middle": # Cut char at middle text_short: str = _truncate_text_middle(text=text_no_ansi, limit=_limit_val) if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi elif len(value) > text_limit: # Shorten value match mode.lower(): case "char": # Cut char at end text_short: str = f"{text_no_ansi[:text_limit]}…" case "word": # Cut path at end # Removing leading/trailing slash if text_no_ansi[-1] == "/": text_no_ansi = text_no_ansi[:-1] if text_no_ansi[0] == "/": text_no_ansi = text_no_ansi[1:] # Split path to count text_parts: list[str] = text_no_ansi.split("/") # Remove last part and slashes from limit text_limit -= len(text_parts[-1]) text_limit -= value.count('/') char_cnt: int = 0 part_at_limit: int = -1 # Find which part exceeds limit for i, part in enumerate(text_parts): char_cnt += len(part) if char_cnt > text_limit: part_at_limit = i break if part_at_limit == -1: # No part exceeds limit text_short: str = text_no_ansi else: # Cut of part, replace with ellipsis part_end: str = text_parts[-1] text_parts = text_parts[:part_at_limit] text_parts.insert(part_at_limit, "…") text_short: str = f"/{'/'.join(text_parts)}/{part_end}" case "middle": # Cut char at middle text_short: str = _truncate_text_middle(text=text_no_ansi, limit=text_limit) else: text_short: str = text_no_ansi # Format Text return LabeledData._format_text(label=label, value=text_short, suffix=suffix_short, quotes=quotes, colon_match=colon_match, no_colon=no_colon, val_color=val_color, label_color=label_color, suffix_color=suffix_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs ) @staticmethod def _shorten_text(label: str, value: str, suffix: str, text: str, limit: int, quotes: bool, val_color: str | tuple[int, int, int] | None = "cyan", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ Generic text shortener for all LabeledData types without a mode. Shortens by char. Internal only. :param label: Label :param value: Value :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :return: Shortened text """ # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) text_no_ansi: str = _remove_ansi(text=value) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' if len(text) <= limit: return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis if len(label_no_ansi) <= limit // 2: label: str = str(label_no_ansi) else: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, value and suffix share limit if len(value) + len(suffix) <= text_limit: text_short: str = text_no_ansi else: text_short: str = '' # Calculate the ratio of text between the value and suffix _ratio: float = len(value) / (len(value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(value) > _limit_val: # Value text_short = f"{text_no_ansi[:_limit_val]}…" else: text_short = text_no_ansi if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi elif len(value) > text_limit: # Shorten value text_short: str = f"{text_no_ansi[:text_limit]}…" else: text_short: str = text_no_ansi # Format Text return LabeledData._format_text(label=label, value=text_short, suffix=suffix_short, quotes=quotes, colon_match=colon_match, no_colon=no_colon, val_color=val_color, label_color=label_color, suffix_color=suffix_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs ) @staticmethod def _shorten_datetime(label: str, value: str, suffix: str, text: str, limit: int, quotes: bool, val_color: str | tuple[int, int, int] | None = "blue", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None) -> str: """ For use with LabeledData.Date. Internal only :param label: Label :param value: Value :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :return: Shortened text """ # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) text_no_ansi: str = _remove_ansi(text=value) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' # Known datetime dividers KNOWN_DIVIDERS: list[str] = [':', '-', '_', '.', ',', ';', '+', '/'] if len(text) <= limit: # Under/at limit return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis if len(label_no_ansi) <= limit // 2: label: str = str(label_no_ansi) else: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, value and suffix share limit if len(value) + len(suffix) <= text_limit: text_short: str = text_no_ansi else: text_short: str = '' # Calculate the ratio of text between the value and suffix _ratio: float = len(value) / (len(value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(value) > _limit_val: # Value # If the value has a known divider, shorten by word # Otherwise, fallback to char if any(div in text_no_ansi for div in KNOWN_DIVIDERS): text_short = textwrap.shorten(text=text_no_ansi, width=_limit_val, placeholder="…") else: text_short = f"{text_no_ansi[:_limit_val]}…" else: text_short = text_no_ansi if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi elif len(value) > text_limit: # If the value has a known divider, shorten by word # Otherwise, fallback to char if any(div in text_no_ansi for div in KNOWN_DIVIDERS): text_short = textwrap.shorten(text=text_no_ansi, width=text_limit, placeholder="…") else: text_short = f"{text_no_ansi[:text_limit]}…" else: text_short: str = text_no_ansi # Format Text return LabeledData._format_text(label=label, value=text_short, suffix=suffix_short, quotes=quotes, colon_match=colon_match, no_colon=no_colon, val_color=val_color, label_color=label_color, suffix_color=suffix_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs ) @staticmethod def _shorten_bytes(label: str, value: int, bytes_hr: float, unit: str, suffix: str, text: str, limit: int, quotes: bool, val_color: str | tuple[int, int, int] | None = "yellow", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, unit_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, unit_match: bool = True, no_colon: bool = False, show_unit: bool = True, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None, unit_attrs: list[str] | None = None) -> str: """ For use with LabeledData.Bytes. Internal only :param label: Label :param value: Value :param bytes_hr: Bytes in human-readable format :param unit: Unit of measurement :param suffix: Suffix :param text: "Label: Value" formatted string :param limit: Maximum length of text :param quotes: If true, wrap text in single quotes (') :param val_color: Color of text. :param label_color: Color of label. :param suffix_color: Color of suffix. :param unit_color: Color of unit. :param colon_match: If true, the colon will match the style of the label. :param unit_match: If true, the unit will match the style of the value. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param show_unit: If true, the unit will be displayed. :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. :param unit_attrs: Attributes to apply to the unit. :return: Shortened text """ # If the value is large (Over 32 bits), format it to E-Notation if bytes_hr and abs(bytes_hr) > 0xffffffff: _value: str = _format_big_number(num=bytes_hr) elif bytes_hr: _value: str = f"{bytes_hr:,.2f}" elif abs(value) > 0xffffffff: _value: str = _format_big_number(num=value) else: _value: str = str(value) # Remove ANSI label_no_ansi: str = _remove_ansi(text=label) unit_no_ansi: str = _remove_ansi(text=unit) suffix_no_ansi: str = _remove_ansi(text=suffix) suffix_short: str = '' if len(text) <= limit: # Under/at limit return text # Calculate limit text_limit: int = limit - len(label_no_ansi) text_limit -= 2 if quotes else 0 # Quotes text_limit -= 2 # Colon and space text_limit -= 1 # Ellipsis # Unit if show_unit: text_limit -= len(unit_no_ansi) + 1 # Unit & space if len(label_no_ansi) <= limit // 2: label: str = str(label_no_ansi) else: # Label needs to be shortened label_old: str = str(label_no_ansi) label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…") # Add back to limit text_limit += len(label_old) - len(label) if suffix: text_limit -= 1 # Space between value and suffix text_limit -= 1 # Space at end # With suffix, value and suffix share limit if len(_value) + len(suffix) > text_limit: # Calculate the ratio of text between the value and suffix _ratio: float = len(_value) / (len(_value) + len(suffix)) _limit_val: int = round(text_limit * _ratio) _limit_suf: int = text_limit - _limit_val if len(suffix) > _limit_suf: # Suffix will always be shortened by word suffix_short = textwrap.shorten(text=suffix_no_ansi, width=_limit_suf, placeholder="…") else: suffix_short = suffix_no_ansi # Format Text return LabeledData._format_bytes(label=label, value=value, bytes_hr=bytes_hr, unit=unit_no_ansi, suffix=suffix_short, quotes=quotes, colon_match=colon_match, unit_match=unit_match, no_colon=no_colon, show_unit=show_unit, val_color=val_color, label_color=label_color, suffix_color=suffix_color, unit_color=unit_color, val_attrs=val_attrs, label_attrs=label_attrs, suffix_attrs=suffix_attrs, unit_attrs=unit_attrs )
[docs] class String:
[docs] def __init__(self, label: str, value: str, suffix: str = '', suffix_color: str | tuple[int, int, int] | None = None, val_color: str | tuple[int, int, int] | None = "cyan", label_color: str | tuple[int, int, int] | None = None, quotes: bool = True, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None): """ Creates a string with a label and string value :param label: Label for string value :param value: String value :param suffix: Optional text to appear after the value :param val_color: Color of string value. :param label_color: Color of label. :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. """ self.label: str = label self.value: str = value self.suffix: str = suffix self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None self.colon_match: bool = colon_match self.no_colon: bool = no_colon self.quotes: bool = quotes self.text_shorten: str = "" # Validate Colors if val_color: _validate_color(color=val_color) self.val_color = val_color.lower() if isinstance(val_color, str) else val_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # Format Text self.text: str = LabeledData._format_text(label=self.label, value=self.value, suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, no_colon=self.no_colon, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs )
def __str__(self): return self.text
[docs] def cutoff_text(self, limit: int, mode: Literal["char", "word", "middle"] = "char") -> str: """ Cuts off text if it exceeds limit. Will first try to shorten string. If there isn't enough space for just an ellipsis, it will try to shorten the label, so that the output will be 50/50 (label: value). Modes: - char: Cutoff char at end of string (The quick brown fox jumped ove…) - word: Cutoff word at end of string (The quick brown fox jumped…) - middle: Cutoff words at middle of string (The quick brown…the lazy dog) :param limit: Maximum length of text :param mode: How to cut off value text :return: Shortened text """ self.text_shorten = LabeledData._shorten_string( label=self.label, value=self.value, suffix=self.suffix, text=self.text, limit=limit, mode=mode, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, colon_match=self.colon_match, no_colon=self.no_colon, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, quotes=self.quotes ) return self.text_shorten
[docs] class Number:
[docs] def __init__(self, label: str, value: int | float, suffix: str = '', round_to: int | None = None, leading_zeros: int | None = None, int_no_round: bool = False, val_color: str | tuple[int, int, int] | None = "yellow", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, quotes: bool = False, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None): """ Creates a string with a label and number value :param label: Label for number value :param value: Number value :param suffix: Optional text to appear after the value :param round_to: Number of decimal places to round to. Leave blank to not round. :param leading_zeros: Number of leading zeros to add to the number. Leave blank to not add leading zeros. :param int_no_round: If true, displays number as integer without rounding. Overrides ``round_to`` :param val_color: Color of number value. :param label_color: Color of label. :param suffix_color: Color of suffix. :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. """ self.label: str = label self.value: int | float = value self.suffix: str = suffix self.round_to: int | None = round_to self.int_no_round: bool = int_no_round self.leading_zeros: int | None = leading_zeros self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.colon_match: bool = colon_match self.no_colon: bool = no_colon self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None self.quotes: bool = quotes self.text_shorten: str = "" # Validate Colors if val_color: _validate_color(color=val_color) self.val_color = val_color.lower() if isinstance(val_color, str) else val_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # If number is large (>32-bit integer), format to E-Notation # Will override rounding and leading zeros if abs(self.value) > 0xffffffff: num_text: str = _format_big_number(num=self.value) else: # Rounding if self.int_no_round: # Display num as integer num_text: str = f"{int(self.value):,}" elif self.round_to is not None and self.round_to > 0: # Round num_text: str = f"{round(self.value, self.round_to):,}" elif self.round_to is not None and self.round_to == 0: # Round to Integer. Don't display trailing 0 num_text: str = f"{round(self.value, self.round_to):,.0f}" elif self.round_to: # Round error raise ValueError("'round' must be an integer greater than 0") else: # No rounding num_text: str = f"{self.value:,}" # Leading Zeros if self.leading_zeros and self.leading_zeros >= 0: num_text = f"{num_text:0{self.leading_zeros}d}" elif self.leading_zeros: # Leading zeros error raise ValueError("'leading_zeros' must be an integer greater than 0") # Format Text self.text: str = LabeledData._format_text(label=self.label, value=num_text, suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, no_colon=self.no_colon, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs )
def __str__(self) -> str: return self.text
[docs] def cutoff_text(self, limit: int) -> str: """ Cuts off text if it exceeds limit. Will NOT shorten the number, only the suffix (if there is one). If there isn't enough space for just an ellipsis, it will try to shorten the label, so that the output will be 50/50 (label: value). :param limit: Maximum length of text :return: Shortened text """ self.text_shorten = LabeledData._shorten_number( label=self.label, value=self.value, suffix=self.suffix, text=self.text, limit=limit, quotes=self.quotes, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, colon_match=self.colon_match, no_colon=self.no_colon, leading_zeros=self.leading_zeros, int_no_round=self.int_no_round, round_to=self.round_to, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, ) return self.text_shorten
[docs] class Boolean:
[docs] def __init__(self, label: str, value: bool, suffix: str = '', t_color: str | tuple[int, int, int] | None = "green", f_color: str | tuple[int, int, int] | None = "red", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, t_text: str = "True", f_text: str = "False", quotes: bool = False, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None): """ Creates a string with a label and boolean value :param label: Label for boolean value :param value: Boolean value :param suffix: Optional text to appear after the value :param t_color: Color of true value. :param f_color: Color of false value. :param label_color: Color of label. :param suffix_color: Color of suffix. :param t_text: Text to display for true value :param f_text: Text to display for false value :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. """ self.label: str = label self.value: bool = value self.suffix: str = suffix self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.val_text: str = t_text if value else f_text self.quotes: bool = quotes self.text_shorten: str = "" self.colon_match: bool = colon_match self.no_colon: bool = no_colon self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None # Validate Colors if value and t_color: _validate_color(color=t_color) self.val_color = t_color elif not value and f_color: _validate_color(color=f_color) self.val_color = f_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # Format Text self.text: str = LabeledData._format_text(label=self.label, value=self.val_text, suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, no_colon=self.no_colon, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, )
def __str__(self): return self.text
[docs] def cutoff_text(self, limit: int) -> str: """ Cuts off text if it exceeds limit. Will first try to shorten bool value. If there isn't enough space for just an ellipsis, it will try to shorten the label, so that the output will be 50/50 (label: value). :param limit: Maximum length of text :return: Shortened text """ self.text_shorten = LabeledData._shorten_text( label=self.label, value=self.val_text, suffix=self.suffix, text=self.text, limit=limit, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, colon_match=self.colon_match, no_colon=self.no_colon, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, quotes=self.quotes ) return self.text_shorten
[docs] class Path:
[docs] def __init__(self, label: str, value: Path | str, suffix: str = '', quotes: bool = True, val_color: str | tuple[int, int, int] | None = "cyan", label_color: str | tuple[int, int, int] | None = None, suffix_color: str | tuple[int, int, int] | None = None, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None): """ Creates a string with a label and path to a file or directory :param label: Label for path :param value: Path to file or directory :param suffix: Optional text to appear after the value :param quotes: If true, wrap path in single quotes (') :param val_color: Color of path value. :param label_color: Color of label. :param suffix_color: Color of suffix. :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param suffix_attrs: Attributes to apply to the suffix. """ self.label: str = label self.quotes: bool = quotes self.suffix: str = suffix self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.colon_match: bool = colon_match self.no_colon: bool = no_colon self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None # Remove redundant quotes from path if self.quotes and isinstance(value, str) and value[0] == "'" and value[-1] == "'": value = value[1:-1] self.value: Path = Path(value) if isinstance(value, str) else value self.text_shorten: str = "" self.value.resolve() # Validate Color if val_color: _validate_color(color=val_color) self.val_color = val_color.lower() if isinstance(val_color, str) else val_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # Format Text self.text: str = LabeledData._format_text(label=self.label, value=str(self.value), suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, no_colon=self.no_colon, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, )
def __str__(self): return self.text
[docs] def cutoff_text(self, limit: int, mode: Literal["char", "word", "middle"] = "char") -> str: """ Shortens path to limit. Modes: - char: Cutoff char at end of string (/path/to/a/f…) - word: Cutoff path at end, preserve the deepest directory/file (/path/to/…/file.txt) - middle: Cutoff chars at middle of string (/long/path/t…/an/file.txt) :param limit: Character limit for full labeled path string :param mode: How to cut off path. :return: LabeledData.Path string with shortened path if it exceeds limit, else returns original string """ self.text_shorten = LabeledData._shorten_path( label=self.label, value=str(self.value), suffix=self.suffix, text=self.text, limit=limit, quotes=self.quotes, mode=mode, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, colon_match=self.colon_match, no_colon=self.no_colon, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, ) return self.text_shorten
[docs] class Date:
[docs] def __init__(self, label: str, value: str | datetime = '', suffix: str = '', strftime: str = '%Y-%m-%d %H:%M:%S', suffix_color: str | tuple[int, int, int] | None = None, val_color: str | tuple[int, int, int] | None = "blue", label_color: str | tuple[int, int, int] | None = None, quotes: bool = True, colon_match: bool = True, no_colon: bool = False, val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None): """ Creates a string with a label and a date value If no value is passed, will print current time using ``strftime`` parameter :param label: Label for string value :param value: Value, either as a datetime object or string :param suffix: Optional text to appear after the value :param strftime: Formatter for datetime value :param val_color: Color of string value. :param label_color: Color of label. :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. """ self.label: str = label self.value: str = value.__str__() self.suffix: str = suffix self.strftime: str = strftime self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None self.colon_match: bool = colon_match self.no_colon: bool = no_colon self.quotes: bool = quotes self.text_shorten: str = "" # Validate Colors if val_color: _validate_color(color=val_color) self.val_color = val_color.lower() if isinstance(val_color, str) else val_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # If strftime was passed, format time if self.strftime and not self.value: # No value, use current time self.value = datetime.now().strftime(self.strftime) elif self.strftime and isinstance(value, datetime): # Format value self.value = value.strftime(self.strftime) # Format Text self.text: str = LabeledData._format_text(label=self.label, value=self.value, suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, no_colon=self.no_colon, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs )
def __str__(self): return self.text
[docs] def cutoff_text(self, limit: int) -> str: """ Cuts off text if it exceeds limit. Will first try to shorten string. If there isn't enough space for just an ellipsis, it will try to shorten the label, so that the output will be 50/50 (label: value). :param limit: Maximum length of text :return: Shortened text """ self.text_shorten = LabeledData._shorten_datetime( label=self.label, value=self.value, suffix=self.suffix, text=self.text, limit=limit, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, colon_match=self.colon_match, no_colon=self.no_colon, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, quotes=self.quotes ) return self.text_shorten
[docs] class Bytes:
[docs] def __init__(self, label: str, value: int, suffix: str = '', suffix_color: str | tuple[int, int, int] | None = None, val_color: str | tuple[int, int, int] | None = "yellow", label_color: str | tuple[int, int, int] | None = None, unit_color: str | tuple[int, int, int] | None = None, quotes: bool = False, colon_match: bool = True, unit_match: bool = True, no_colon: bool = False, show_unit: bool = True, unit_type: Literal["iec", "si"] = "iec", val_attrs: list[str] | None = None, label_attrs: list[str] | None = None, suffix_attrs: list[str] | None = None, unit_attrs: list[str] | None = None): """ Creates a string with a label and bytes value :param label: Label for string value :param value: Integer value. Value MUST be in bytes :param suffix: Optional text to appear after the value :param val_color: Color of string value. :param label_color: Color of label. :param unit_color: Color of unit. Overrides ``unit_match``. :param quotes: If true, wrap text in single quotes (') :param colon_match: If true, the colon will match the style of the label. :param unit_match: If true, the unit will match the style of the value. :param no_colon: If true, the colon will be removed. Overrides ``colon_match`` :param show_unit: If true, the unit will be shown. :param unit_type: Unit type: IEC is base 2, SI is base 10 :param val_attrs: Attributes to apply to the value. :param label_attrs: Attributes to apply to the label. :param unit_attrs: Attributes to apply to the unit. Overrides ``unit_match``. """ self.label: str = label self.value: int = value self.suffix: str = suffix self.val_color: str | tuple[int, int, int] | None = None self.label_color: str | tuple[int, int, int] | None = None self.suffix_color: str | tuple[int, int, int] | None = None self.unit_color: str | tuple[int, int, int] | None = None self.val_attrs: list[str] | None = None self.label_attrs: list[str] | None = None self.suffix_attrs: list[str] | None = None self.unit_attrs: list[str] | None = None self.colon_match: bool = colon_match self.unit_match: bool = unit_match self.no_colon: bool = no_colon self.show_unit: bool = show_unit self.unit_type: Literal["iec", "si"] = unit_type self.quotes: bool = quotes self.text_shorten: str = "" self.unit: str = '' self.byte_val: float = 0 # Validate Colors if val_color: _validate_color(color=val_color) self.val_color = val_color.lower() if isinstance(val_color, str) else val_color if label_color: _validate_color(color=label_color) self.label_color = label_color.lower() if isinstance(label_color, str) else label_color if suffix_color: _validate_color(color=suffix_color) self.suffix_color = suffix_color.lower() if isinstance(suffix_color, str) else suffix_color if unit_color: _validate_color(color=unit_color) self.unit_color = unit_color.lower() if isinstance(unit_color, str) else unit_color # Validate Attributes if val_attrs: _validate_attrs(attrs=val_attrs) self.val_attrs = val_attrs if label_attrs: _validate_attrs(attrs=label_attrs) self.label_attrs = label_attrs if suffix_attrs: _validate_attrs(attrs=suffix_attrs) self.suffix_attrs = suffix_attrs if unit_attrs: _validate_attrs(attrs=unit_attrs) self.unit_attrs = unit_attrs # Remove redundant colon from label if self.label[-1] == ":": self.label = self.label[:-1] # Get unit if self.show_unit: self.byte_val, self.unit = _human_readable_bytes(value=self.value, unit_type=self.unit_type) # Format Text self.text: str = LabeledData._format_bytes(label=self.label, value=self.value, bytes_hr=self.byte_val, unit=self.unit, suffix=self.suffix, quotes=self.quotes, colon_match=self.colon_match, unit_match=self.unit_match, no_colon=self.no_colon, show_unit=self.show_unit, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, unit_color=self.unit_color, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, unit_attrs=self.unit_attrs )
def __str__(self): return self.text
[docs] def cutoff_text(self, limit: int) -> str: """ Cuts off text if it exceeds limit. Will NOT shorten the value, only suffix (if there is one). If there isn't enough space for just an ellipsis, it will try to shorten the label, so that the output will be 50/50 (label: value). :param limit: Maximum length of text :return: Shortened text """ self.text_shorten = LabeledData._shorten_bytes( label=self.label, value=self.value, bytes_hr=self.byte_val, unit=self.unit, suffix=self.suffix, text=self.text, limit=limit, val_color=self.val_color, label_color=self.label_color, suffix_color=self.suffix_color, unit_color=self.unit_color, colon_match=self.colon_match, unit_match=self.unit_match, no_colon=self.no_colon, show_unit=self.show_unit, val_attrs=self.val_attrs, label_attrs=self.label_attrs, suffix_attrs=self.suffix_attrs, unit_attrs=self.unit_attrs, quotes=self.quotes ) return self.text_shorten
# Typing alias AnyLabeledData = LabeledData.String | LabeledData.Number | LabeledData.Boolean | LabeledData.Path | LabeledData.Date \ | LabeledData.Bytes