A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such as bold, italic etc. The attributes have 3 states: they can either be on
(``True``), off (``False``), ornot set (``None``).
Args:
color (Union[Color, str], optional): Color of terminal text. Defaults to None.
bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
bold (bool, optional): Enable bold text. Defaults to None.
dim (bool, optional): Enable dim text. Defaults to None.
italic (bool, optional): Enable italic text. Defaults to None.
underline (bool, optional): Enable underlined text. Defaults to None.
blink (bool, optional): Enabled blinking text. Defaults to None.
blink2 (bool, optional): Enable fast blinking text. Defaults to None.
reverse (bool, optional): Enabled reverse text. Defaults to None.
conceal (bool, optional): Enable concealed text. Defaults to None.
strike (bool, optional): Enable strikethrough text. Defaults to None.
underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
frame (bool, optional): Enable framed text. Defaults to None.
encircle (bool, optional): Enable encircled text. Defaults to None.
overline (bool, optional): Enable overlined text. Defaults to None.
link (str, link): Link URL. Defaults to None.
"""
_color: Optional[Color]
_bgcolor: Optional[Color]
_attributes: int
_set_attributes: int
_hash: Optional[int]
_null: bool
_meta: Optional[bytes]
self._link = link
self._meta = Noneif meta isNoneelse dumps(meta)
self._link_id = (
f"{randint(0, 999999)}{hash(self._meta)}"if (link or meta) else""
)
self._hash: Optional[int] = None
self._null = not (self._set_attributes or color or bgcolor or link or meta)
@classmethod def null(cls) -> "Style": """Create an 'null' style, equivalent to Style(), but more performant.""" return NULL_STYLE
@classmethod def from_color(
cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
) -> "Style": """Create a new style with colors and no attributes.
Returns:
color (Optional[Color]): A (foreground) color, orNonefor no color. Defaults to None.
bgcolor (Optional[Color]): A (background) color, orNonefor no color. Defaults to None. """
style: Style = cls.__new__(Style)
style._ansi = None
style._style_definition = None
style._color = color
style._bgcolor = bgcolor
style._set_attributes = 0
style._attributes = 0
style._link = None
style._link_id = ""
style._meta = None
style._null = not (color or bgcolor)
style._hash = None return style
@classmethod def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style": """Create a new style with meta data.
Returns:
meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None. """
style: Style = cls.__new__(Style)
style._ansi = None
style._style_definition = None
style._color = None
style._bgcolor = None
style._set_attributes = 0
style._attributes = 0
style._link = None
style._meta = dumps(meta)
style._link_id = f"{randint(0, 999999)}{hash(style._meta)}"
style._hash = None
style._null = not (meta) return style
@classmethod def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style": """Create a blank style with meta information.
Example:
style = Style.on(click=self.on_click)
Args:
meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
**handlers (Any): Keyword arguments are translated in to handlers.
Returns:
Style: A Style with meta information attached. """
meta = {} if meta isNoneelse meta
meta.update({f"@{key}": value for key, value in handlers.items()}) return cls.from_meta(meta)
@property def link_id(self) -> str: """Get a link id, used in ansi code for links.""" return self._link_id
def __str__(self) -> str: """Re-generate style definition from attributes.""" if self._style_definition isNone:
attributes: List[str] = []
append = attributes.append
bits = self._set_attributes if bits & 0b0000000001111: if bits & 1:
append("bold"if self.bold else"not bold") if bits & (1 << 1):
append("dim"if self.dim else"not dim") if bits & (1 << 2):
append("italic"if self.italic else"not italic") if bits & (1 << 3):
append("underline"if self.underline else"not underline") if bits & 0b0000111110000: if bits & (1 << 4):
append("blink"if self.blink else"not blink") if bits & (1 << 5):
append("blink2"if self.blink2 else"not blink2") if bits & (1 << 6):
append("reverse"if self.reverse else"not reverse") if bits & (1 << 7):
append("conceal"if self.conceal else"not conceal") if bits & (1 << 8):
append("strike"if self.strike else"not strike") if bits & 0b1111000000000: if bits & (1 << 9):
append("underline2"if self.underline2 else"not underline2") if bits & (1 << 10):
append("frame"if self.frame else"not frame") if bits & (1 << 11):
append("encircle"if self.encircle else"not encircle") if bits & (1 << 12):
append("overline"if self.overline else"not overline") if self._color isnotNone:
append(self._color.name) if self._bgcolor isnotNone:
append("on")
append(self._bgcolor.name) if self._link:
append("link")
append(self._link)
self._style_definition = " ".join(attributes) or"none" return self._style_definition
def __bool__(self) -> bool: """A Style is false if it has no attributes, colors, or links.""" returnnot self._null
def _make_ansi_codes(self, color_system: ColorSystem) -> str: """Generate ANSI codes for this style.
Args:
color_system (ColorSystem): Color system.
Returns:
str: String containing codes. """
if self._ansi isNone:
sgr: List[str] = []
append = sgr.append
_style_map = self._style_map
attributes = self._attributes & self._set_attributes if attributes: if attributes & 1:
append(_style_map[0]) if attributes & 2:
append(_style_map[1]) if attributes & 4:
append(_style_map[2]) if attributes & 8:
append(_style_map[3]) if attributes & 0b0000111110000: for bit in range(4, 9): if attributes & (1 << bit):
append(_style_map[bit]) if attributes & 0b1111000000000: for bit in range(9, 13): if attributes & (1 << bit):
append(_style_map[bit]) if self._color isnotNone:
sgr.extend(self._color.downgrade(color_system).get_ansi_codes()) if self._bgcolor isnotNone:
sgr.extend(
self._bgcolor.downgrade(color_system).get_ansi_codes(
foreground=False
)
)
self._ansi = ";".join(sgr) return self._ansi
@classmethod
@lru_cache(maxsize=1024) def normalize(cls, style: str) -> str: """Normalize a style definition so that styles with the same effect have the same string
representation.
Args:
style (str): A style definition.
Returns:
str: Normal form of style definition. """ try: return str(cls.parse(style)) except errors.StyleSyntaxError: return style.strip().lower()
@classmethod def pick_first(cls, *values: Optional[StyleType]) -> StyleType: """Pick first non-None style.""" for value in values: if value isnotNone: return value raise ValueError("expected at least one non-None style")
@property def meta(self) -> Dict[str, Any]: """Get meta information (can not be changed after construction).""" return {} if self._meta isNoneelse cast(Dict[str, Any], loads(self._meta))
@property def without_color(self) -> "Style": """Get a copy of the style with color removed.""" if self._null: return NULL_STYLE
style: Style = self.__new__(Style)
style._ansi = None
style._style_definition = None
style._color = None
style._bgcolor = None
style._attributes = self._attributes
style._set_attributes = self._set_attributes
style._link = self._link
style._link_id = f"{randint(0, 999999)}"if self._link else""
style._null = False
style._meta = None
style._hash = None return style
words = iter(style_definition.split()) for original_word in words:
word = original_word.lower() if word == "on":
word = next(words, "") ifnot word: raise errors.StyleSyntaxError("color expected after 'on'") try:
Color.parse(word) isNone except ColorParseError as error: raise errors.StyleSyntaxError(
f"unable to parse {word!r} as background color; {error}"
) fromNone
bgcolor = word
elif word == "not":
word = next(words, "")
attribute = STYLE_ATTRIBUTES.get(word) if attribute isNone: raise errors.StyleSyntaxError(
f"expected style attribute after 'not', found {word!r}"
)
attributes[attribute] = False
elif word == "link":
word = next(words, "") ifnot word: raise errors.StyleSyntaxError("URL expected after 'link'")
link = word
elif word in STYLE_ATTRIBUTES:
attributes[STYLE_ATTRIBUTES[word]] = True
else: try:
Color.parse(word) except ColorParseError as error: raise errors.StyleSyntaxError(
f"unable to parse {word!r} as color; {error}"
) fromNone
color = word
style = Style(color=color, bgcolor=bgcolor, link=link, **attributes) return style
color = self.color
bgcolor = self.bgcolor if self.reverse:
color, bgcolor = bgcolor, color if self.dim:
foreground_color = (
theme.foreground_color if color isNoneelse color.get_truecolor(theme)
)
color = Color.from_triplet(
blend_rgb(foreground_color, theme.background_color, 0.5)
) if color isnotNone:
theme_color = color.get_truecolor(theme)
append(f"color: {theme_color.hex}")
append(f"text-decoration-color: {theme_color.hex}") if bgcolor isnotNone:
theme_color = bgcolor.get_truecolor(theme, foreground=False)
append(f"background-color: {theme_color.hex}") if self.bold:
append("font-weight: bold") if self.italic:
append("font-style: italic") if self.underline:
append("text-decoration: underline") if self.strike:
append("text-decoration: line-through") if self.overline:
append("text-decoration: overline") return"; ".join(css)
@classmethod def combine(cls, styles: Iterable["Style"]) -> "Style": """Combine styles and get result.
Args:
styles (Iterable[Style]): Styles to combine.
Returns:
Style: A new style instance. """
iter_styles = iter(styles) return sum(iter_styles, next(iter_styles))
@classmethod def chain(cls, *styles: "Style") -> "Style": """Combine styles from positional argument in to a single style.
Args:
*styles (Iterable[Style]): Styles to combine.
Returns:
Style: A new style instance. """
iter_styles = iter(styles) return sum(iter_styles, next(iter_styles))
def copy(self) -> "Style": """Get a copy of this style.
def render(
self,
text: str = "",
*,
color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
legacy_windows: bool = False,
) -> str: """Render the ANSI codes for the style.
Args:
text (str, optional): A string to style. Defaults to "".
color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
Returns:
str: A string containing ANSI style codes. """ ifnot text or color_system isNone: return text
attrs = self._ansi or self._make_ansi_codes(color_system)
rendered = f"\x1b[{attrs}m{text}\x1b[0m"if attrs else text if self._link andnot legacy_windows:
rendered = (
f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
) return rendered
def test(self, text: Optional[str] = None) -> None: """Write text with style directly to terminal.
This method isfor testing purposes only.
Args:
text (Optional[str], optional): Text to style orNonefor style name.
"""
text = text or str(self)
sys.stdout.write(f"{self.render(text)}\n")
@lru_cache(maxsize=1024) def _add(self, style: Optional["Style"]) -> "Style": if style isNoneor style._null: return self if self._null: return style
new_style: Style = self.__new__(Style)
new_style._ansi = None
new_style._style_definition = None
new_style._color = style._color or self._color
new_style._bgcolor = style._bgcolor or self._bgcolor
new_style._attributes = (self._attributes & ~style._set_attributes) | (
style._attributes & style._set_attributes
)
new_style._set_attributes = self._set_attributes | style._set_attributes
new_style._link = style._link or self._link
new_style._link_id = style._link_id or self._link_id
new_style._null = style._null if self._meta and style._meta:
new_style._meta = dumps({**self.meta, **style.meta}) else:
new_style._meta = self._meta or style._meta
new_style._hash = None return new_style
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.