Source code for framed_text.status

import textwrap
from sys import stdout
from typing import Literal

from termcolor import colored as col

from framed_text.labeled_data import AnyLabeledData
from framed_text.utils import (
    _validate_color,
    _remove_ansi,
    _validate_attrs,
    _format_col_text,
    _truncate_text_middle,
    ANSIEscape,
    _ansi_pipe_clean,
    _get_terminal_width
)


[docs] class StatusColors: """ A collection of the default colors used by the Status classes. Pre-formatted Available Icons --------------- - **INFO**: ``'?'`` - **ACTION**: ``'➤'`` - **SUCCESS**: ``'✔'`` - **FAIL**: ``'✗'`` - **WARN**: ``'⚠️'`` - **HIDDEN**: ``'➤'`` """ INFO: str = "yellow" ACTION: str = "blue" SUCCESS: str = "green" FAIL: str = "red" WARN: tuple[int, int, int] = (239, 202, 19) HIDDEN: tuple[int, int, int] = (125, 125, 125)
[docs] class StatusIcons: """ A collection of the default icons used by the Status classes. Pre-formatted Available Icons --------------- - **INFO**: ``'?'`` - **ACTION**: ``'➤'`` - **SUCCESS**: ``'✔'`` - **FAIL**: ``'✗'`` - **WARN**: ``'⚠️'`` - **HIDDEN**: ``'➤'`` """ INFO: str = _ansi_pipe_clean(text=col('?', StatusColors.INFO)) ACTION: str = _ansi_pipe_clean(text=col('➤', StatusColors.ACTION)) SUCCESS: str = _ansi_pipe_clean(text=col('✔', StatusColors.SUCCESS)) FAIL: str = _ansi_pipe_clean(text=col('✗', StatusColors.FAIL)) WARN: str = _ansi_pipe_clean(text=col('⚠️', StatusColors.WARN)) HIDDEN: str = _ansi_pipe_clean(text=col('➤', StatusColors.HIDDEN))
[docs] class BaseStatus:
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '>', icon_color: str | tuple[int, int, int] | None = None, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = None, msg_attrs: list[str] | None = None, overwrite: bool = False): """ Status base class. Can be used to create custom status classes. Predefined Status classes are available in the Status class. :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. If no icon, seperator is not used. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes of the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes of the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """ # Misc variables self._overwrite: bool = overwrite # Icon self.icon: str = icon self.icon_color: str | tuple[int, int, int] | None = None self.icon_attrs: list[str] | None = None if icon_color: _validate_color(color=icon_color) self.icon_color = icon_color if icon_attrs: _validate_attrs(attrs=icon_attrs) self.icon_attrs = icon_attrs # Message self.seperator: str = sep self.message: str | AnyLabeledData | None = msg self.text_color: str | tuple[int, int, int] | None = None self.text_attrs: list[str] | None = None self.text_shorten: str = '' if msg_color: _validate_color(color=msg_color) self.text_color = msg_color if msg_attrs: _validate_attrs(attrs=msg_attrs) self.text_attrs = msg_attrs self._term_width: int = _get_terminal_width() # --- Setup text --- # Icon if self.icon: icon: str = _format_col_text(text=self.icon, color=self.icon_color, attrs=self.icon_attrs) else: icon: str = '' # Seperator if self.seperator: seperator: str = self.seperator else: seperator: str = '' # Message if self.message and isinstance(self.message, AnyLabeledData): # Handle LabeledData separately self.message.cutoff_text(limit=self._term_width - len(icon) - len(seperator)) message: str = self.message.text_shorten elif self.message: message: str = _format_col_text(text=self.message.__str__(), color=self.text_color, attrs=self.text_attrs) else: message: str = '' if self.icon: self.text: str = f"{icon}{seperator}{message}" else: self.text: str = str(message)
def __str__(self): if self._overwrite: # Move up and delete previous line stdout.write(ANSIEscape.move_up()) stdout.write(ANSIEscape.ERASE_LINE) return self.text
[docs] def cutoff_text(self, limit: int, mode: Literal["char", "word", "middle"] = "char") -> str: """ Cutoffs text if it exceeds limit. Icon and seperator will always be shortened by character. **Modes** - ``char``: Cutoff char at end of text (The quick brown fox jumped ove…) - ``word``: Cutoff word at end of text (The quick brown fox jumped…) - ``middle``: Cutoff chars at middle of text (The quick brown…the lazy dog) :param limit: Maximum length of text :param mode: Mode to use for shortening. See above for info. :return: Cutoff text """ if len(self.text) <= limit: return self.text # Calculate text limit text_limit: int = limit - len(self.icon) text_limit -= len(self.seperator) text_limit -= 1 # Ellipsis icon_no_ansi: str = _remove_ansi(text=self.icon) msg_no_ansi: str = _remove_ansi(text=self.message.__str__()) if len(icon_no_ansi) > limit // 2: # Shorten icon icon: str = icon_no_ansi[:(limit // 2) - 1] + '…' # Add back difference text_limit += len(icon_no_ansi) - len(icon) else: icon: str = icon_no_ansi if len(self.seperator) > 10: # Shorten seperator seperator: str = self.seperator[:10] # Add back difference text_limit += len(self.seperator) - len(seperator) else: seperator: str = self.seperator if self.message and len(msg_no_ansi) > text_limit: match mode.lower(): case "char": # Shorten by char msg_short: str = f"{msg_no_ansi[:text_limit]}…" case "word": # Shorten by word msg_short: str = textwrap.shorten(text=msg_no_ansi, width=text_limit, placeholder='…') case "middle": # Shorten by middle characters msg_short: str = _truncate_text_middle(text=msg_no_ansi, limit=text_limit) elif self.message: msg_short: str = msg_no_ansi else: msg_short: str = '' # Format Text if self.icon: icon = _format_col_text(text=icon, color=self.icon_color, attrs=self.icon_attrs) if self.message: msg_short = _format_col_text(text=msg_short, color=self.text_color, attrs=self.text_attrs) if self.icon: self.text_shorten: str = f"{icon}{seperator}{msg_short}" else: self.text_shorten: str = str(msg_short) return self.text_shorten
def _shorten_icon(self, limit: int) -> str: """ Shorten icon to fit limit :param limit: Maximum length of icon :return: Shortened icon """ if len(self.icon) > limit: self.icon_short = self.icon[:limit - 1] + '…' return self.icon_short def _shorten_message(self, limit: int, mode: Literal["char", "word"] = "char") -> str: """ Shorten message to fit limit :param limit: Maximum length of message :param mode: Mode to use for shortening. "char" for character, "word" for word :return: Shortened message """ if len(self.message.__str__()) > limit: match mode.lower(): case "char": # Shorten by Character self.message_short = self.message[:limit - 1] + '…' case "word": # Shorten by Word msg_no_ansi: str = _remove_ansi(text=self.message.__str__()) self.message_short = textwrap.shorten(text=msg_no_ansi, width=limit, placeholder='…') case _: raise ValueError(f"Invalid mode: '{mode}'") return self.message_short
[docs] class Status: """ Predefined Status classes. Can be used with LabeledData and regular strings. Each Status optionally takes a message and text color which creates a string in the format: `<icon> <message>` Custom Status classes can be created from the `BaseStatus` class. """ # Status Subclasses
[docs] class Info(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '?', icon_color: str | tuple[int, int, int] = StatusColors.INFO, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = None, msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
[docs] class Action(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '➤', icon_color: str | tuple[int, int, int] = StatusColors.ACTION, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = None, msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
[docs] class Success(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '✔', icon_color: str | tuple[int, int, int] = StatusColors.SUCCESS, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = StatusColors.SUCCESS, msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
[docs] class Fail(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '✗', icon_color: str | tuple[int, int, int] = StatusColors.FAIL, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = StatusColors.FAIL, msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
[docs] class Warn(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '⚠️', icon_color: str | tuple[int, int, int] = StatusColors.WARN, icon_attrs: list[str] | None = None, sep: str = '', msg_color: str | tuple[int, int, int] | None = "yellow", msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
[docs] class Hidden(BaseStatus): """ A special status which shows the text in a dark color. An alternative to ``Status.Action`` :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param icon_color: Color of the icon. Can be a termcolor color code or an RGB tuple :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param sep: Seperator between icon and message. Will only be used when message is provided. :param msg_color: Color of the message. Can be a termcolor color code or an RGB tuple :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes :param overwrite: If true, will overwrite the previous line with a new status. """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = '➤', icon_color: str | tuple[int, int, int] = StatusColors.HIDDEN, icon_attrs: list[str] | None = None, sep: str = ' ', msg_color: str | tuple[int, int, int] | None = StatusColors.HIDDEN, msg_attrs: list[str] | None = None, overwrite: bool = False): super().__init__(msg=msg, icon=icon, icon_color=icon_color, icon_attrs=icon_attrs, sep=sep, msg_color=msg_color, msg_attrs=msg_attrs, overwrite=overwrite)
# Typing Alias AnyStatus = Status.Info | Status.Action | Status.Success | Status.Fail | Status.Warn | Status.Hidden