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.color import Color
from framed_text.labeled_data import AnyLabeledData
from framed_text.utils import (
    _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 Colors ---------------- - **INFO**: ``"yellow"`` - **ACTION**: ``"blue"`` - **SUCCESS**: ``"green"`` - **FAIL**: ``"red"`` - **WARN**: ``(239, 202, 19)`` - **HIDDEN**: ``(125, 125, 125)`` """ 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 = '>', sep: str = ' ', overwrite: bool = False, icon_color: Color | None = None, msg_color: Color | None = None, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): """ 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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes of the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes of the message. Can be a list of termcolor attributes """ self._msg: str | AnyLabeledData | None = msg self._icon: str = icon self._sep: str = sep self._overwrite: bool = overwrite self._icon_color: Color = icon_color if icon_color else Color() self._msg_color: Color = msg_color if msg_color else Color() self._icon_attrs: list[str] | None = _validate_attrs(attrs=icon_attrs) self._msg_attrs: list[str] | None = _validate_attrs(attrs=msg_attrs) self._term_width: int = _get_terminal_width() self.text: str = self._format_text() self.text_shorten: str = ''
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 def _format_text(self) -> str: """ Format the status message :return: Status message """ _icon: str = '' _sep: str = '' _msg: str = '' if self._icon: _icon = _format_col_text(text=self._icon, color=self._icon_color.tc_color, attrs=self._icon_attrs) if self._sep: _sep = self._sep if self._msg is not None and isinstance(self._msg, AnyLabeledData): # Handle LabeledData separately self._msg.cutoff_text(limit=self._term_width - len(_icon) - len(_sep)) _msg = self._msg.text_shorten elif self._msg is not None: _msg = _format_col_text(text=self._msg.__str__(), color=self._msg_color.tc_color, attrs=self._msg_attrs) if self._icon: return f"{_icon}{_sep}{_msg}" else: return _msg
[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: # Text below limit. Nothing to do return self.text # Remove ANSI icon_no_ansi: str = _remove_ansi(text=self._icon) sep_no_ansi: str = _remove_ansi(text=self._sep) msg_no_ansi: str = _remove_ansi(text=self._msg.__str__()) ina_len: int = len(icon_no_ansi) sna_len: int = len(sep_no_ansi) mna_len: int = len(msg_no_ansi) # Calculate text limit text_limit: int = limit - (ina_len + sna_len) # Icon + sep text_limit -= 1 # Ellipsis if len(icon_no_ansi) <= limit // 2: # Icon below limit. _icon: str = icon_no_ansi else: # Shorten icon _icon: str = f"{icon_no_ansi[:(limit // 2) - 1]}…" # Add back difference text_limit += ina_len - len(_icon) if sna_len <= 10: # Separator below limit. _sep: str = sep_no_ansi else: # Shorten separator _sep: str = sep_no_ansi[:10] # Add back difference text_limit += sna_len - len(_sep) _msg: str = '' if msg_no_ansi and mna_len > text_limit: match mode.lower(): case "char": # Shorten by char _msg: str = f"{msg_no_ansi[:text_limit]}…" case "word": # Shorten by word _msg: str = textwrap.shorten(text=msg_no_ansi, width=text_limit, placeholder='…') case "middle": # Shorten by middle characters _msg: str = _truncate_text_middle(text=msg_no_ansi, limit=text_limit) elif msg_no_ansi: _msg: str = msg_no_ansi # Format Text if _icon: _icon = _format_col_text(text=_icon, color=self._icon_color.tc_color, attrs=self._icon_attrs) if _msg: _msg = _format_col_text(text=_msg, color=self._msg_color.tc_color, attrs=self._msg_attrs) if _icon: self.text_shorten: str = f"{_icon}{_sep}{_msg}" else: self.text_shorten: str = str(_msg) return self.text_shorten
[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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.INFO, sep: str = ' ', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.INFO, msg_color: str | tuple[int, int, int] | Color | None = None, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
[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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.ACTION, sep: str = ' ', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.ACTION, msg_color: str | tuple[int, int, int] | Color | None = None, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
[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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.SUCCESS, sep: str = ' ', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.SUCCESS, msg_color: str | tuple[int, int, int] | Color | None = StatusColors.SUCCESS, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
[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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.FAIL, sep: str = ' ', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.FAIL, msg_color: str | tuple[int, int, int] | Color | None = StatusColors.FAIL, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
[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 sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.WARN, sep: str = '', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.WARN, msg_color: str | tuple[int, int, int] | Color | None = "yellow", icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
[docs] class Hidden(BaseStatus): """ :param msg: Message to display after the icon :param icon: Character(s) to use as the icon. Icon can be any size. :param sep: Separator between icon and message. Will only be used when message is provided. :param overwrite: If true, will overwrite the previous line with a new status. :param icon_color: Color of the icon. :param msg_color: Color of the message. :param icon_attrs: Attributes to apply to the icon. Can be a list of termcolor attributes :param msg_attrs: Attributes to apply to the message. Can be a list of termcolor attributes """
[docs] def __init__(self, msg: str | AnyLabeledData | None = None, icon: str = StatusIcons.HIDDEN, sep: str = ' ', overwrite: bool = False, icon_color: str | tuple[int, int, int] | Color | None = StatusColors.HIDDEN, msg_color: str | tuple[int, int, int] | Color | None = StatusColors.HIDDEN, icon_attrs: list[str] | None = None, msg_attrs: list[str] | None = None): super().__init__( msg=msg, icon=icon, sep=sep, overwrite=overwrite, icon_color=Color(icon_color) if icon_color else None, msg_color=Color(msg_color) if msg_color else None, icon_attrs=icon_attrs, msg_attrs=msg_attrs )
def __str__(self): return super().__str__()
# Typing Alias AnyStatus = Status.Info | Status.Action | Status.Success | Status.Fail | Status.Warn | Status.Hidden