import textwrap
from datetime import datetime
from pathlib import Path
from typing import Literal, Any
from framed_text.color import Color
from framed_text.utils import (
_format_col_text,
_format_big_number,
_human_readable_bytes,
_remove_ansi,
_truncate_text_middle,
_validate_attrs,
)
[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.
"""
class _Base:
def __init__(self, ld_type: str,
label: str,
value: Any,
suffix: str = '',
quotes: bool = True,
colon_match: bool = True,
no_colon: bool = False,
label_color: Color | None = None,
val_color: Color | None = None,
suffix_color: Color | None = None,
label_attrs: list[str] | None = None,
val_attrs: list[str] | None = None,
suffix_attrs: list[str] | None = None,
**params):
"""
Base class for LabeledData classes.
:param ld_type: Type of LabeledData. Used for formatting different data types.
:param label: Label
:param value: Value
:param suffix: Optional text to appear after the 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 label_color: Color of label.
:param val_color: Color of string value.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param suffix_color: Color of suffix
:param params: Additional class-specific parameters
"""
self._ld_type: str = ld_type.lower()
self._label: str = label
self._value: Any = value
self._suffix: str = suffix
self._quotes: bool = quotes
self._colon_match: bool = colon_match
self._no_colon: bool = no_colon
self._label_color: Color = label_color if label_color else Color()
self._val_color: Color = val_color if val_color else Color()
self._suffix_color: Color = suffix_color if suffix_color else Color()
self._label_attrs: list[str] | None = _validate_attrs(attrs=label_attrs)
self._val_attrs: list[str] | None = _validate_attrs(attrs=val_attrs)
self._suffix_attrs: list[str] | None = _validate_attrs(attrs=suffix_attrs)
self._params: dict[str, Any] = params
# Parse params
self._parse_params()
self.text_shorten: str = ''
self.text: str = self._format_text()
def __str__(self) -> str:
return self.text
def _parse_params(self):
"""
Parse any additional parameters passed to the class.
``self._value`` will be updated based on params.
"""
match self._ld_type:
case "number":
# If number is large (>32-bit integer), format to E-Notation
# Will override rounding and leading zeros
if abs(self._value) > 0xffffffff:
self._value = _format_big_number(num=self._value)
else:
# Rounding
if self._params["int_no_round"]:
# Display num as integer
self._value = f"{int(self._value):,}"
elif self._params["round_to"] is not None and self._params["round_to"] > 0:
# Round
self._value = f"{round(self._value, self._params["round_to"]):,}"
elif self._params["round_to"] is not None and self._params["round_to"] == 0:
# Round to Integer. Don't display trailing 0
self._value = f"{round(self._value, self._params["round_to"]):,.0f}"
elif self._params["round_to"]:
# Round error
raise ValueError("'round' must be an integer greater than 0")
else:
# No rounding
self._value = f"{self._value:,}"
# Leading Zeros
if self._params["leading_zeros"] and self._params["leading_zeros"] >= 0:
self._value = f"{self._value:0{self._params["leading_zeros"]}d}"
elif self._params["leading_zeros"]:
# Leading zeros error
raise ValueError("'leading_zeros' must be an integer greater than 0")
case "date":
# If strftime was passed, format time
if self._params["strftime"] and not self._value:
# No value, use current time
self._value = datetime.now().strftime(self._params["strftime"])
elif self._params["strftime"] and isinstance(self._value, datetime):
# Format value
self._value = self._value.strftime(self._params["strftime"])
case "bytes":
# Validate unit style vars
self._params["unit_color"] = self._params["unit_color"] if self._params["unit_color"] else Color()
self._params["unit_attrs"] = _validate_attrs(attrs=self._params["unit_attrs"])
def _format_text(self, label: str = '', value: str = '', suffix: str = '') -> str:
# Label
_label: str = _format_col_text(text=label if label else self._label,
color=self._label_color.tc_color,
attrs=self._label_attrs)
# Colon if needed
if self._colon_match and not self._no_colon:
_col: str = _format_col_text(text=':', color=self._label_color.tc_color)
elif not self._no_colon:
_col: str = ':'
else:
_col: str = ''
# Suffix
_suffix: str = _format_col_text(text=suffix if suffix else self._suffix,
color=self._suffix_color.tc_color,
attrs=self._suffix_attrs)
# Value
if value:
_value: str = _format_col_text(text=f"'{value}'" if self._quotes else value,
color=self._val_color.tc_color,
attrs=self._val_attrs)
elif self._ld_type == "bytes":
# Unit & Value
_byte_val, _unit = _human_readable_bytes(value=self._value,
unit_type=self._params["unit_type"])
_unit_color: Color = self._params["unit_color"] or Color()
_unit_attrs: list[str] | None = self._params["unit_attrs"]
# Add unit to params
self._params["unit"] = _unit
# Override unit_match if a unit color/attr is passed
if self._params["unit_match"]:
_unit_color = self._val_color if not _unit_color.tc_color else _unit_color
_unit_attrs = self._val_attrs if not _unit_attrs else _unit_attrs
if self._params["show_unit"]:
# If the value is large (Over 32 bits), format it to E-Notation
if _byte_val and abs(_byte_val) > 0xffffffff:
_val: str = _format_big_number(num=_byte_val)
elif _byte_val:
_val: str = f"{_byte_val:,.2f}"
elif abs(self._value) > 0xffffffff:
_val: str = _format_big_number(num=self._value)
else:
_val: str = str(self._value)
_unit_txt: str = _format_col_text(text=_unit,
color=_unit_color.tc_color,
attrs=_unit_attrs)
else:
_val: str = str(self._value)
_unit_txt: str = ''
_val = _format_col_text(text=_val,
color=self._val_color.tc_color,
attrs=self._val_attrs)
_value: str = f"'{_val} {_unit_txt}'" if self._quotes else f"{_val} {_unit_txt}"
else:
if self._quotes:
_value: str = _format_col_text(text=f"'{self._value}'",
color=self._val_color.tc_color,
attrs=self._val_attrs)
else:
_value: str = _format_col_text(text=str(self._value),
color=self._val_color.tc_color,
attrs=self._val_attrs)
if self._suffix != '':
return f"{_label}{_col} {_value} {_suffix}"
else:
return f"{_label}{_col} {_value}"
def _cutoff_text(self, limit: int,
mode: Literal["char", "word", "middle"] | None = None) -> str:
"""
Main types of text cutoff:
- String: Shorten everything
- Numbers: Shorten everything, except value (Bytes: unit)
- Path: Custom version of String
"""
if len(self.text) <= limit:
# Under or at limit
return self.text
# Limit calc
_limit: int = limit - 2 if self._quotes else limit # Quotes
_limit -= 1 if self._no_colon else 2 # Colon + space
# Setup mode
_mode: Literal["char", "word", "middle"] = "char"
if self._ld_type == "date":
_mode = "word"
elif mode:
_mode = mode
_suffix: str = ''
match self._ld_type:
case "string" | "boolean" | "date":
# Shorten everything
_limit, _label = self._shorten_label(label=self._label, limit=_limit)
if self._suffix:
_value, _suffix = self._shorten_val_suffix(value=str(self._value),
suffix=self._suffix,
limit=_limit,
mode=_mode)
else:
_limit, _value = self._shorten_value(value=str(self._value), limit=_limit,
mode=_mode)
# Format text
self.text_shorten = self._format_text(label=_label, value=_value, suffix=_suffix)
case "number" | "bytes":
# Shorten everything, except value
# Bytes also doesn't shorten the unit
_limit, _label = self._shorten_label(label=self._label, limit=_limit)
# Calculate limit for suffix
vna_len: int = len(_remove_ansi(str(self._value)))
_limit -= vna_len
if self._ld_type == "bytes":
# Unit calculation
una_len: int = len(_remove_ansi(self._params["unit"]))
_limit -= una_len
# Ensure there's enough space for suffix ellipsis
_limit = max(1, _limit - 1)
_suffix = self._shorten_suffix(suffix=self._suffix, limit=_limit)
# Format text
self.text_shorten = self._format_text(label=_label, suffix=_suffix)
case _:
# Same as string, except custom logic for value
_limit, _label = self._shorten_label(label=self._label, limit=_limit)
# --------------------------------------------------------------------------------------------
# Other LD types with different logic go below here
# --------------------------------------------------------------------------------------------
_value: str = ''
if self._ld_type == "path":
_value, _suffix = self._shorten_path(value=str(self._value), suffix=self._suffix,
limit=_limit, mode=_mode)
# Format text
if self._suffix != '':
self.text_shorten = self._format_text(label=_label, value=_value, suffix=_suffix)
else:
self.text_shorten = self._format_text(label=_label, value=_value)
return self.text_shorten
# ============================================================================================
# HELPER FUNCTIONS FOR CUTOFF GO HERE
# ============================================================================================
@staticmethod
def _get_val_suffix_limits(vna_len: int, sna_len: int, limit: int) -> tuple[int, int]:
"""
Get the value and suffix limits
:param vna_len: Length of value without ANSI
:param sna_len: Length of suffix without ANSI
:param limit: # of characters to limit the value and suffix to
:return: value limit, suffix limit
"""
# Calculate limit
_limit: int = limit - 1 if sna_len > 0 else limit # Space before suffix
_limit -= 1 # Value ellipsis due to char mode
if vna_len + sna_len <= _limit:
# Value and suffix under limit. No work needed
return -1, -1
# Calculate ratio between value and suffix
favor_ratio: float = 0.75
if vna_len <= (_limit * favor_ratio):
# Favor value
limit_ratio: float = favor_ratio
# Only take up as much space as needed
favor_len: int = round(_limit * limit_ratio)
value_limit: int = min(vna_len, favor_len)
suffix_limit: int = _limit - value_limit
else:
# Favor whichever is longer
limit_ratio: float = vna_len / (vna_len + sna_len)
value_limit: int = round(_limit * limit_ratio)
suffix_limit: int = _limit - value_limit
return value_limit, suffix_limit
# ============================================================================================
# FUNCTIONS FOR CUTOFF: Each function should handle a specific part of the text
# ============================================================================================
@staticmethod
def _shorten_label(label: str, limit: int) -> tuple[int, str]:
"""
Shorten the label portion of the text
:param label: Label
:param limit: # of characters to limit the label to
:return: Limit, shortened label
"""
# Remove ANSI
label_no_ansi: str = _remove_ansi(text=label)
lna_len: int = len(label_no_ansi)
# Calculate label's limit
label_limit: int = limit - lna_len
if lna_len <= limit // 2:
# Label under limit. No work needed
return label_limit, label
# Shorten label
_label: str = textwrap.shorten(text=label_no_ansi, width=limit // 2, placeholder="…")
l_len: int = len(_label)
# Add back removed characters to limit
label_limit += lna_len - l_len
return label_limit, _label
@staticmethod
def _shorten_value(value: str, limit: int,
mode: Literal["char", "word", "middle"]) -> tuple[int, str]:
"""
Shorten the value portion of the text
:param value: Value
:param limit: # of characters to limit the value to
:param mode: How the text should be shortened. See documentation for details
:return: Limit, shortened value
"""
# Remove ANSI
value_no_ansi: str = _remove_ansi(text=value)
vna_len: int = len(value_no_ansi)
limit -= 1 # Ellipsis due to char mode
if vna_len <= limit:
# Value under limit. No work needed
return limit, value
# Shorten value
_value: str = ''
match mode.lower():
case "char":
# Shorten by char
_value = f"{value_no_ansi[:limit]}…"
case "word":
# Shorten by word
_value = textwrap.shorten(text=value_no_ansi, width=limit, placeholder="…")
case "middle":
# Shorten by middle characters
_value = _truncate_text_middle(text=value_no_ansi, limit=limit)
# Update limit
v_len: int = len(_value)
value_limit: int = limit + (vna_len - v_len) # Add back removed characters
value_limit -= v_len # Subtract new length
return value_limit, _value
@staticmethod
def _shorten_val_suffix(value: str, suffix: str, limit: int,
mode: Literal["char", "word", "middle"]) -> tuple[str, str]:
"""
Shorten both the value and suffix portions of the text.
Each will share a ratio of the limit.
:param value: Value
:param limit: # of characters to limit the value to
:param mode: How the text should be shortened. See documentation for details
:return: Shortened value & suffix
"""
# Remove ANSI
value_no_ansi: str = _remove_ansi(text=value)
suffix_no_ansi: str = _remove_ansi(text=suffix)
vna_len: int = len(value_no_ansi)
sna_len: int = len(suffix_no_ansi)
# Get limits
value_limit, suffix_limit = LabeledData._Base._get_val_suffix_limits(vna_len=vna_len,
sna_len=sna_len,
limit=limit)
if value_limit == -1 and suffix_limit == -1:
# Value and suffix under limit. No work needed
return value, suffix
# Shorten value if necessary
_value: str = value_no_ansi
if vna_len > value_limit:
match mode.lower():
case "char":
# Shorten by char
_value = f"{value_no_ansi[:value_limit]}…"
case "word":
# Shorten by word
_value = textwrap.shorten(text=value_no_ansi, width=value_limit, placeholder="…")
case "middle":
# Shorten by middle characters
_value = _truncate_text_middle(text=value_no_ansi, limit=value_limit)
# Shorten suffix if necessary
_suffix: str = suffix_no_ansi
if sna_len > suffix_limit:
# Suffix will always be shortened by word
_suffix = textwrap.shorten(text=suffix_no_ansi, width=suffix_limit, placeholder="…")
return _value, _suffix
# ============================================================================================
# DATA-SPECIFIC CUTOFF FUNCTIONS GO HERE
# ============================================================================================
@staticmethod
def _shorten_suffix(suffix: str, limit: int) -> str:
"""
Shorten only the suffix. Limit with value should have been calculated
:param suffix: Suffix
:param limit: # of characters to limit the value to
:return: Shortened suffix
"""
# Remove ANSI
suffix_no_ansi: str = _remove_ansi(text=suffix)
sna_len: int = len(suffix_no_ansi)
if sna_len <= limit:
# Suffix under limit. No work needed
return suffix
# Shorten suffix
return textwrap.shorten(text=suffix_no_ansi, width=limit, placeholder="…")
@staticmethod
def _shorten_path(value: str, suffix: str, limit: int,
mode: Literal["char", "word", "middle"]) -> tuple[str, str]:
"""
Shorten the value and suffix (if any) of a path
:param value: Value
:param suffix: Suffix
:param limit: # of characters to limit the value to
:param mode: How the text should be shortened. See documentation for details
:return: Shortened value and suffix (if any)
"""
# Remove ANSI
value_no_ansi: str = _remove_ansi(text=value)
suffix_no_ansi: str = _remove_ansi(text=suffix)
vna_len: int = len(value_no_ansi)
sna_len: int = len(suffix_no_ansi)
# Get limits
value_limit, suffix_limit = LabeledData._Base._get_val_suffix_limits(vna_len=vna_len,
sna_len=sna_len,
limit=limit)
if value_limit == -1 and suffix_limit == -1:
# Value and suffix under limit. No work needed
return value, suffix
# Shorten value if necessary
_value: str = value_no_ansi
if vna_len > value_limit:
match mode.lower():
case "char":
# Shorten by char
_value = f"{value_no_ansi[:value_limit]}…"
case "word":
# Shorten by word
# Removing leading/trailing slash
if value_no_ansi[-1] == "/":
value_no_ansi = value_no_ansi[:-1]
if value_no_ansi[0] == "/":
value_no_ansi = value_no_ansi[1:]
# Split path to count
text_parts: list[str] = value_no_ansi.split("/")
# Remove last part and slashes from limit
value_limit -= len(text_parts[-1])
value_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 > value_limit:
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, "…")
_value = f"/{'/'.join(text_parts)}/{part_end}"
else:
# No part exceeds limit
_value = value_no_ansi
case "middle":
# Shorten by middle characters
_value = _truncate_text_middle(text=value_no_ansi, limit=value_limit)
# Shorten suffix if necessary
_suffix: str = suffix_no_ansi
if sna_len > suffix_limit:
# Suffix will always be shortened by word
_suffix = textwrap.shorten(text=suffix_no_ansi, width=suffix_limit, placeholder="…")
return _value, _suffix
[docs]
class String(_Base):
[docs]
def __init__(self, label: str,
value: str,
suffix: str = '',
quotes: bool = True,
colon_match: bool = True,
no_colon: bool = False,
label_color: str | tuple[int, int, int] | Color | None = None,
val_color: str | tuple[int, int, int] | Color | None = "cyan",
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_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 suffix_color: Color of suffix
: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.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=value,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(val_color) if val_color else None,
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs
)
def __str__(self) -> str:
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit, mode=mode)
[docs]
class Number(_Base):
[docs]
def __init__(self, label: str,
value: int | float,
suffix: str = '',
quotes: bool = False,
colon_match: bool = True,
no_colon: bool = False,
round_to: int | None = None,
leading_zeros: int | None = None,
int_no_round: bool = False,
label_color: str | tuple[int, int, int] | Color | None = None,
val_color: str | tuple[int, int, int] | Color | None = "yellow",
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_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 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 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 label_color: Color of label.
:param val_color: Color of number value.
:param suffix_color: Color of suffix.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param suffix_attrs: Attributes to apply to the suffix.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=value,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(val_color) if val_color else None,
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs,
# Extra
round_to=round_to,
leading_zeros=leading_zeros,
int_no_round=int_no_round
)
def __str__(self) -> str:
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit)
[docs]
class Boolean(_Base):
[docs]
def __init__(self, label: str,
value: bool,
suffix: str = '',
t_text: str = "True",
f_text: str = "False",
quotes: bool = False,
colon_match: bool = True,
no_colon: bool = False,
label_color: str | tuple[int, int, int] | Color | None = None,
t_color: str | tuple[int, int, int] | Color | None = "green",
f_color: str | tuple[int, int, int] | Color | None = "red",
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_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_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 label_color: Color of label.
:param t_color: Color of true value.
:param f_color: Color of false value.
:param suffix_color: Color of suffix.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param suffix_attrs: Attributes to apply to the suffix.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=t_text if value else f_text,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(t_color) if value else Color(f_color),
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs,
)
def __str__(self):
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit)
[docs]
class Path(_Base):
[docs]
def __init__(self, label: str,
value: Path | str,
suffix: str = '',
quotes: bool = True,
colon_match: bool = True,
no_colon: bool = False,
label_color: str | tuple[int, int, int] | Color | None = None,
val_color: str | tuple[int, int, int] | Color | None = "cyan",
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_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 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 label_color: Color of label.
:param val_color: Color of path value.
:param suffix_color: Color of suffix.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param suffix_attrs: Attributes to apply to the suffix.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=value,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(val_color) if val_color else None,
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs,
)
def __str__(self):
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit, mode=mode)
[docs]
class Date(_Base):
[docs]
def __init__(self, label: str,
value: str | datetime = '',
suffix: str = '',
quotes: bool = True,
colon_match: bool = True,
no_colon: bool = False,
strftime: str = '%Y-%m-%d %H:%M:%S',
label_color: str | tuple[int, int, int] | Color | None = None,
val_color: str | tuple[int, int, int] | Color | None = "blue",
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_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 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 strftime: Formatter for datetime value
:param label_color: Color of label.
:param val_color: Color of string value.
:param suffix_color: Color of suffix.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param suffix_attrs: Attributes to apply to the suffix.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=value,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(val_color) if val_color else None,
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs,
# Extra
strftime=strftime,
)
def __str__(self):
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit)
[docs]
class Bytes(_Base):
[docs]
def __init__(self, label: str,
value: int,
suffix: str = '',
quotes: bool = False,
colon_match: bool = True,
no_colon: bool = False,
unit_match: bool = True,
show_unit: bool = True,
unit_type: Literal["iec", "si"] = "iec",
label_color: str | tuple[int, int, int] | Color | None = None,
val_color: str | tuple[int, int, int] | Color | None = "yellow",
unit_color: str | tuple[int, int, int] | Color | None = None,
suffix_color: str | tuple[int, int, int] | Color | None = None,
label_attrs: list[str] | None = None,
val_attrs: list[str] | None = None,
unit_attrs: list[str] | None = None,
suffix_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 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 show_unit: If true, the unit will be shown.
:param unit_match: If true, the unit will match the style of the value.
:param unit_type: Unit type: IEC is base 2, SI is base 10
:param label_color: Color of label.
:param val_color: Color of string value.
:param unit_color: Color of unit. Overrides ``unit_match``.
:param suffix_color: Color of suffix.
:param label_attrs: Attributes to apply to the label.
:param val_attrs: Attributes to apply to the value.
:param unit_attrs: Attributes to apply to the unit. Overrides ``unit_match``.
:param suffix_attrs: Attributes to apply to the suffix.
"""
super().__init__(
# Main
ld_type=self.__class__.__name__,
label=label,
value=value,
suffix=suffix,
quotes=quotes,
colon_match=colon_match,
no_colon=no_colon,
label_color=Color(label_color) if label_color else None,
val_color=Color(val_color) if val_color else None,
suffix_color=Color(suffix_color) if suffix_color else None,
label_attrs=label_attrs,
val_attrs=val_attrs,
suffix_attrs=suffix_attrs,
# Extra
show_unit=show_unit,
unit_match=unit_match,
unit_type=unit_type,
unit_color=Color(unit_color) if unit_color else None,
unit_attrs=unit_attrs,
)
def __str__(self):
return super().__str__()
[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
"""
return super()._cutoff_text(limit=limit)
# Typing alias
AnyLabeledData = LabeledData.String | LabeledData.Number | LabeledData.Boolean | LabeledData.Path | LabeledData.Date \
| LabeledData.Bytes