python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_vendor/
rich/
style.py
       1  import sys
       2  from functools import lru_cache
       3  from marshal import dumps, loads
       4  from random import randint
       5  from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast
       6  
       7  from . import errors
       8  from .color import Color, ColorParseError, ColorSystem, blend_rgb
       9  from .repr import Result, rich_repr
      10  from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
      11  
      12  # Style instances and style definitions are often interchangeable
      13  StyleType = Union[str, "Style"]
      14  
      15  
      16  class ESC[4;38;5;81m_Bit:
      17      """A descriptor to get/set a style attribute bit."""
      18  
      19      __slots__ = ["bit"]
      20  
      21      def __init__(self, bit_no: int) -> None:
      22          self.bit = 1 << bit_no
      23  
      24      def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]:
      25          if obj._set_attributes & self.bit:
      26              return obj._attributes & self.bit != 0
      27          return None
      28  
      29  
      30  @rich_repr
      31  class ESC[4;38;5;81mStyle:
      32      """A terminal style.
      33  
      34      A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such
      35      as bold, italic etc. The attributes have 3 states: they can either be on
      36      (``True``), off (``False``), or not set (``None``).
      37  
      38      Args:
      39          color (Union[Color, str], optional): Color of terminal text. Defaults to None.
      40          bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
      41          bold (bool, optional): Enable bold text. Defaults to None.
      42          dim (bool, optional): Enable dim text. Defaults to None.
      43          italic (bool, optional): Enable italic text. Defaults to None.
      44          underline (bool, optional): Enable underlined text. Defaults to None.
      45          blink (bool, optional): Enabled blinking text. Defaults to None.
      46          blink2 (bool, optional): Enable fast blinking text. Defaults to None.
      47          reverse (bool, optional): Enabled reverse text. Defaults to None.
      48          conceal (bool, optional): Enable concealed text. Defaults to None.
      49          strike (bool, optional): Enable strikethrough text. Defaults to None.
      50          underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
      51          frame (bool, optional): Enable framed text. Defaults to None.
      52          encircle (bool, optional): Enable encircled text. Defaults to None.
      53          overline (bool, optional): Enable overlined text. Defaults to None.
      54          link (str, link): Link URL. Defaults to None.
      55  
      56      """
      57  
      58      _color: Optional[Color]
      59      _bgcolor: Optional[Color]
      60      _attributes: int
      61      _set_attributes: int
      62      _hash: Optional[int]
      63      _null: bool
      64      _meta: Optional[bytes]
      65  
      66      __slots__ = [
      67          "_color",
      68          "_bgcolor",
      69          "_attributes",
      70          "_set_attributes",
      71          "_link",
      72          "_link_id",
      73          "_ansi",
      74          "_style_definition",
      75          "_hash",
      76          "_null",
      77          "_meta",
      78      ]
      79  
      80      # maps bits on to SGR parameter
      81      _style_map = {
      82          0: "1",
      83          1: "2",
      84          2: "3",
      85          3: "4",
      86          4: "5",
      87          5: "6",
      88          6: "7",
      89          7: "8",
      90          8: "9",
      91          9: "21",
      92          10: "51",
      93          11: "52",
      94          12: "53",
      95      }
      96  
      97      STYLE_ATTRIBUTES = {
      98          "dim": "dim",
      99          "d": "dim",
     100          "bold": "bold",
     101          "b": "bold",
     102          "italic": "italic",
     103          "i": "italic",
     104          "underline": "underline",
     105          "u": "underline",
     106          "blink": "blink",
     107          "blink2": "blink2",
     108          "reverse": "reverse",
     109          "r": "reverse",
     110          "conceal": "conceal",
     111          "c": "conceal",
     112          "strike": "strike",
     113          "s": "strike",
     114          "underline2": "underline2",
     115          "uu": "underline2",
     116          "frame": "frame",
     117          "encircle": "encircle",
     118          "overline": "overline",
     119          "o": "overline",
     120      }
     121  
     122      def __init__(
     123          self,
     124          *,
     125          color: Optional[Union[Color, str]] = None,
     126          bgcolor: Optional[Union[Color, str]] = None,
     127          bold: Optional[bool] = None,
     128          dim: Optional[bool] = None,
     129          italic: Optional[bool] = None,
     130          underline: Optional[bool] = None,
     131          blink: Optional[bool] = None,
     132          blink2: Optional[bool] = None,
     133          reverse: Optional[bool] = None,
     134          conceal: Optional[bool] = None,
     135          strike: Optional[bool] = None,
     136          underline2: Optional[bool] = None,
     137          frame: Optional[bool] = None,
     138          encircle: Optional[bool] = None,
     139          overline: Optional[bool] = None,
     140          link: Optional[str] = None,
     141          meta: Optional[Dict[str, Any]] = None,
     142      ):
     143          self._ansi: Optional[str] = None
     144          self._style_definition: Optional[str] = None
     145  
     146          def _make_color(color: Union[Color, str]) -> Color:
     147              return color if isinstance(color, Color) else Color.parse(color)
     148  
     149          self._color = None if color is None else _make_color(color)
     150          self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
     151          self._set_attributes = sum(
     152              (
     153                  bold is not None,
     154                  dim is not None and 2,
     155                  italic is not None and 4,
     156                  underline is not None and 8,
     157                  blink is not None and 16,
     158                  blink2 is not None and 32,
     159                  reverse is not None and 64,
     160                  conceal is not None and 128,
     161                  strike is not None and 256,
     162                  underline2 is not None and 512,
     163                  frame is not None and 1024,
     164                  encircle is not None and 2048,
     165                  overline is not None and 4096,
     166              )
     167          )
     168          self._attributes = (
     169              sum(
     170                  (
     171                      bold and 1 or 0,
     172                      dim and 2 or 0,
     173                      italic and 4 or 0,
     174                      underline and 8 or 0,
     175                      blink and 16 or 0,
     176                      blink2 and 32 or 0,
     177                      reverse and 64 or 0,
     178                      conceal and 128 or 0,
     179                      strike and 256 or 0,
     180                      underline2 and 512 or 0,
     181                      frame and 1024 or 0,
     182                      encircle and 2048 or 0,
     183                      overline and 4096 or 0,
     184                  )
     185              )
     186              if self._set_attributes
     187              else 0
     188          )
     189  
     190          self._link = link
     191          self._meta = None if meta is None else dumps(meta)
     192          self._link_id = (
     193              f"{randint(0, 999999)}{hash(self._meta)}" if (link or meta) else ""
     194          )
     195          self._hash: Optional[int] = None
     196          self._null = not (self._set_attributes or color or bgcolor or link or meta)
     197  
     198      @classmethod
     199      def null(cls) -> "Style":
     200          """Create an 'null' style, equivalent to Style(), but more performant."""
     201          return NULL_STYLE
     202  
     203      @classmethod
     204      def from_color(
     205          cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
     206      ) -> "Style":
     207          """Create a new style with colors and no attributes.
     208  
     209          Returns:
     210              color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
     211              bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
     212          """
     213          style: Style = cls.__new__(Style)
     214          style._ansi = None
     215          style._style_definition = None
     216          style._color = color
     217          style._bgcolor = bgcolor
     218          style._set_attributes = 0
     219          style._attributes = 0
     220          style._link = None
     221          style._link_id = ""
     222          style._meta = None
     223          style._null = not (color or bgcolor)
     224          style._hash = None
     225          return style
     226  
     227      @classmethod
     228      def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style":
     229          """Create a new style with meta data.
     230  
     231          Returns:
     232              meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None.
     233          """
     234          style: Style = cls.__new__(Style)
     235          style._ansi = None
     236          style._style_definition = None
     237          style._color = None
     238          style._bgcolor = None
     239          style._set_attributes = 0
     240          style._attributes = 0
     241          style._link = None
     242          style._meta = dumps(meta)
     243          style._link_id = f"{randint(0, 999999)}{hash(style._meta)}"
     244          style._hash = None
     245          style._null = not (meta)
     246          return style
     247  
     248      @classmethod
     249      def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style":
     250          """Create a blank style with meta information.
     251  
     252          Example:
     253              style = Style.on(click=self.on_click)
     254  
     255          Args:
     256              meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
     257              **handlers (Any): Keyword arguments are translated in to handlers.
     258  
     259          Returns:
     260              Style: A Style with meta information attached.
     261          """
     262          meta = {} if meta is None else meta
     263          meta.update({f"@{key}": value for key, value in handlers.items()})
     264          return cls.from_meta(meta)
     265  
     266      bold = _Bit(0)
     267      dim = _Bit(1)
     268      italic = _Bit(2)
     269      underline = _Bit(3)
     270      blink = _Bit(4)
     271      blink2 = _Bit(5)
     272      reverse = _Bit(6)
     273      conceal = _Bit(7)
     274      strike = _Bit(8)
     275      underline2 = _Bit(9)
     276      frame = _Bit(10)
     277      encircle = _Bit(11)
     278      overline = _Bit(12)
     279  
     280      @property
     281      def link_id(self) -> str:
     282          """Get a link id, used in ansi code for links."""
     283          return self._link_id
     284  
     285      def __str__(self) -> str:
     286          """Re-generate style definition from attributes."""
     287          if self._style_definition is None:
     288              attributes: List[str] = []
     289              append = attributes.append
     290              bits = self._set_attributes
     291              if bits & 0b0000000001111:
     292                  if bits & 1:
     293                      append("bold" if self.bold else "not bold")
     294                  if bits & (1 << 1):
     295                      append("dim" if self.dim else "not dim")
     296                  if bits & (1 << 2):
     297                      append("italic" if self.italic else "not italic")
     298                  if bits & (1 << 3):
     299                      append("underline" if self.underline else "not underline")
     300              if bits & 0b0000111110000:
     301                  if bits & (1 << 4):
     302                      append("blink" if self.blink else "not blink")
     303                  if bits & (1 << 5):
     304                      append("blink2" if self.blink2 else "not blink2")
     305                  if bits & (1 << 6):
     306                      append("reverse" if self.reverse else "not reverse")
     307                  if bits & (1 << 7):
     308                      append("conceal" if self.conceal else "not conceal")
     309                  if bits & (1 << 8):
     310                      append("strike" if self.strike else "not strike")
     311              if bits & 0b1111000000000:
     312                  if bits & (1 << 9):
     313                      append("underline2" if self.underline2 else "not underline2")
     314                  if bits & (1 << 10):
     315                      append("frame" if self.frame else "not frame")
     316                  if bits & (1 << 11):
     317                      append("encircle" if self.encircle else "not encircle")
     318                  if bits & (1 << 12):
     319                      append("overline" if self.overline else "not overline")
     320              if self._color is not None:
     321                  append(self._color.name)
     322              if self._bgcolor is not None:
     323                  append("on")
     324                  append(self._bgcolor.name)
     325              if self._link:
     326                  append("link")
     327                  append(self._link)
     328              self._style_definition = " ".join(attributes) or "none"
     329          return self._style_definition
     330  
     331      def __bool__(self) -> bool:
     332          """A Style is false if it has no attributes, colors, or links."""
     333          return not self._null
     334  
     335      def _make_ansi_codes(self, color_system: ColorSystem) -> str:
     336          """Generate ANSI codes for this style.
     337  
     338          Args:
     339              color_system (ColorSystem): Color system.
     340  
     341          Returns:
     342              str: String containing codes.
     343          """
     344  
     345          if self._ansi is None:
     346              sgr: List[str] = []
     347              append = sgr.append
     348              _style_map = self._style_map
     349              attributes = self._attributes & self._set_attributes
     350              if attributes:
     351                  if attributes & 1:
     352                      append(_style_map[0])
     353                  if attributes & 2:
     354                      append(_style_map[1])
     355                  if attributes & 4:
     356                      append(_style_map[2])
     357                  if attributes & 8:
     358                      append(_style_map[3])
     359                  if attributes & 0b0000111110000:
     360                      for bit in range(4, 9):
     361                          if attributes & (1 << bit):
     362                              append(_style_map[bit])
     363                  if attributes & 0b1111000000000:
     364                      for bit in range(9, 13):
     365                          if attributes & (1 << bit):
     366                              append(_style_map[bit])
     367              if self._color is not None:
     368                  sgr.extend(self._color.downgrade(color_system).get_ansi_codes())
     369              if self._bgcolor is not None:
     370                  sgr.extend(
     371                      self._bgcolor.downgrade(color_system).get_ansi_codes(
     372                          foreground=False
     373                      )
     374                  )
     375              self._ansi = ";".join(sgr)
     376          return self._ansi
     377  
     378      @classmethod
     379      @lru_cache(maxsize=1024)
     380      def normalize(cls, style: str) -> str:
     381          """Normalize a style definition so that styles with the same effect have the same string
     382          representation.
     383  
     384          Args:
     385              style (str): A style definition.
     386  
     387          Returns:
     388              str: Normal form of style definition.
     389          """
     390          try:
     391              return str(cls.parse(style))
     392          except errors.StyleSyntaxError:
     393              return style.strip().lower()
     394  
     395      @classmethod
     396      def pick_first(cls, *values: Optional[StyleType]) -> StyleType:
     397          """Pick first non-None style."""
     398          for value in values:
     399              if value is not None:
     400                  return value
     401          raise ValueError("expected at least one non-None style")
     402  
     403      def __rich_repr__(self) -> Result:
     404          yield "color", self.color, None
     405          yield "bgcolor", self.bgcolor, None
     406          yield "bold", self.bold, None,
     407          yield "dim", self.dim, None,
     408          yield "italic", self.italic, None
     409          yield "underline", self.underline, None,
     410          yield "blink", self.blink, None
     411          yield "blink2", self.blink2, None
     412          yield "reverse", self.reverse, None
     413          yield "conceal", self.conceal, None
     414          yield "strike", self.strike, None
     415          yield "underline2", self.underline2, None
     416          yield "frame", self.frame, None
     417          yield "encircle", self.encircle, None
     418          yield "link", self.link, None
     419          if self._meta:
     420              yield "meta", self.meta
     421  
     422      def __eq__(self, other: Any) -> bool:
     423          if not isinstance(other, Style):
     424              return NotImplemented
     425          return self.__hash__() == other.__hash__()
     426  
     427      def __ne__(self, other: Any) -> bool:
     428          if not isinstance(other, Style):
     429              return NotImplemented
     430          return self.__hash__() != other.__hash__()
     431  
     432      def __hash__(self) -> int:
     433          if self._hash is not None:
     434              return self._hash
     435          self._hash = hash(
     436              (
     437                  self._color,
     438                  self._bgcolor,
     439                  self._attributes,
     440                  self._set_attributes,
     441                  self._link,
     442                  self._meta,
     443              )
     444          )
     445          return self._hash
     446  
     447      @property
     448      def color(self) -> Optional[Color]:
     449          """The foreground color or None if it is not set."""
     450          return self._color
     451  
     452      @property
     453      def bgcolor(self) -> Optional[Color]:
     454          """The background color or None if it is not set."""
     455          return self._bgcolor
     456  
     457      @property
     458      def link(self) -> Optional[str]:
     459          """Link text, if set."""
     460          return self._link
     461  
     462      @property
     463      def transparent_background(self) -> bool:
     464          """Check if the style specified a transparent background."""
     465          return self.bgcolor is None or self.bgcolor.is_default
     466  
     467      @property
     468      def background_style(self) -> "Style":
     469          """A Style with background only."""
     470          return Style(bgcolor=self.bgcolor)
     471  
     472      @property
     473      def meta(self) -> Dict[str, Any]:
     474          """Get meta information (can not be changed after construction)."""
     475          return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta))
     476  
     477      @property
     478      def without_color(self) -> "Style":
     479          """Get a copy of the style with color removed."""
     480          if self._null:
     481              return NULL_STYLE
     482          style: Style = self.__new__(Style)
     483          style._ansi = None
     484          style._style_definition = None
     485          style._color = None
     486          style._bgcolor = None
     487          style._attributes = self._attributes
     488          style._set_attributes = self._set_attributes
     489          style._link = self._link
     490          style._link_id = f"{randint(0, 999999)}" if self._link else ""
     491          style._null = False
     492          style._meta = None
     493          style._hash = None
     494          return style
     495  
     496      @classmethod
     497      @lru_cache(maxsize=4096)
     498      def parse(cls, style_definition: str) -> "Style":
     499          """Parse a style definition.
     500  
     501          Args:
     502              style_definition (str): A string containing a style.
     503  
     504          Raises:
     505              errors.StyleSyntaxError: If the style definition syntax is invalid.
     506  
     507          Returns:
     508              `Style`: A Style instance.
     509          """
     510          if style_definition.strip() == "none" or not style_definition:
     511              return cls.null()
     512  
     513          STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES
     514          color: Optional[str] = None
     515          bgcolor: Optional[str] = None
     516          attributes: Dict[str, Optional[Any]] = {}
     517          link: Optional[str] = None
     518  
     519          words = iter(style_definition.split())
     520          for original_word in words:
     521              word = original_word.lower()
     522              if word == "on":
     523                  word = next(words, "")
     524                  if not word:
     525                      raise errors.StyleSyntaxError("color expected after 'on'")
     526                  try:
     527                      Color.parse(word) is None
     528                  except ColorParseError as error:
     529                      raise errors.StyleSyntaxError(
     530                          f"unable to parse {word!r} as background color; {error}"
     531                      ) from None
     532                  bgcolor = word
     533  
     534              elif word == "not":
     535                  word = next(words, "")
     536                  attribute = STYLE_ATTRIBUTES.get(word)
     537                  if attribute is None:
     538                      raise errors.StyleSyntaxError(
     539                          f"expected style attribute after 'not', found {word!r}"
     540                      )
     541                  attributes[attribute] = False
     542  
     543              elif word == "link":
     544                  word = next(words, "")
     545                  if not word:
     546                      raise errors.StyleSyntaxError("URL expected after 'link'")
     547                  link = word
     548  
     549              elif word in STYLE_ATTRIBUTES:
     550                  attributes[STYLE_ATTRIBUTES[word]] = True
     551  
     552              else:
     553                  try:
     554                      Color.parse(word)
     555                  except ColorParseError as error:
     556                      raise errors.StyleSyntaxError(
     557                          f"unable to parse {word!r} as color; {error}"
     558                      ) from None
     559                  color = word
     560          style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
     561          return style
     562  
     563      @lru_cache(maxsize=1024)
     564      def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str:
     565          """Get a CSS style rule."""
     566          theme = theme or DEFAULT_TERMINAL_THEME
     567          css: List[str] = []
     568          append = css.append
     569  
     570          color = self.color
     571          bgcolor = self.bgcolor
     572          if self.reverse:
     573              color, bgcolor = bgcolor, color
     574          if self.dim:
     575              foreground_color = (
     576                  theme.foreground_color if color is None else color.get_truecolor(theme)
     577              )
     578              color = Color.from_triplet(
     579                  blend_rgb(foreground_color, theme.background_color, 0.5)
     580              )
     581          if color is not None:
     582              theme_color = color.get_truecolor(theme)
     583              append(f"color: {theme_color.hex}")
     584              append(f"text-decoration-color: {theme_color.hex}")
     585          if bgcolor is not None:
     586              theme_color = bgcolor.get_truecolor(theme, foreground=False)
     587              append(f"background-color: {theme_color.hex}")
     588          if self.bold:
     589              append("font-weight: bold")
     590          if self.italic:
     591              append("font-style: italic")
     592          if self.underline:
     593              append("text-decoration: underline")
     594          if self.strike:
     595              append("text-decoration: line-through")
     596          if self.overline:
     597              append("text-decoration: overline")
     598          return "; ".join(css)
     599  
     600      @classmethod
     601      def combine(cls, styles: Iterable["Style"]) -> "Style":
     602          """Combine styles and get result.
     603  
     604          Args:
     605              styles (Iterable[Style]): Styles to combine.
     606  
     607          Returns:
     608              Style: A new style instance.
     609          """
     610          iter_styles = iter(styles)
     611          return sum(iter_styles, next(iter_styles))
     612  
     613      @classmethod
     614      def chain(cls, *styles: "Style") -> "Style":
     615          """Combine styles from positional argument in to a single style.
     616  
     617          Args:
     618              *styles (Iterable[Style]): Styles to combine.
     619  
     620          Returns:
     621              Style: A new style instance.
     622          """
     623          iter_styles = iter(styles)
     624          return sum(iter_styles, next(iter_styles))
     625  
     626      def copy(self) -> "Style":
     627          """Get a copy of this style.
     628  
     629          Returns:
     630              Style: A new Style instance with identical attributes.
     631          """
     632          if self._null:
     633              return NULL_STYLE
     634          style: Style = self.__new__(Style)
     635          style._ansi = self._ansi
     636          style._style_definition = self._style_definition
     637          style._color = self._color
     638          style._bgcolor = self._bgcolor
     639          style._attributes = self._attributes
     640          style._set_attributes = self._set_attributes
     641          style._link = self._link
     642          style._link_id = f"{randint(0, 999999)}" if self._link else ""
     643          style._hash = self._hash
     644          style._null = False
     645          style._meta = self._meta
     646          return style
     647  
     648      @lru_cache(maxsize=128)
     649      def clear_meta_and_links(self) -> "Style":
     650          """Get a copy of this style with link and meta information removed.
     651  
     652          Returns:
     653              Style: New style object.
     654          """
     655          if self._null:
     656              return NULL_STYLE
     657          style: Style = self.__new__(Style)
     658          style._ansi = self._ansi
     659          style._style_definition = self._style_definition
     660          style._color = self._color
     661          style._bgcolor = self._bgcolor
     662          style._attributes = self._attributes
     663          style._set_attributes = self._set_attributes
     664          style._link = None
     665          style._link_id = ""
     666          style._hash = self._hash
     667          style._null = False
     668          style._meta = None
     669          return style
     670  
     671      def update_link(self, link: Optional[str] = None) -> "Style":
     672          """Get a copy with a different value for link.
     673  
     674          Args:
     675              link (str, optional): New value for link. Defaults to None.
     676  
     677          Returns:
     678              Style: A new Style instance.
     679          """
     680          style: Style = self.__new__(Style)
     681          style._ansi = self._ansi
     682          style._style_definition = self._style_definition
     683          style._color = self._color
     684          style._bgcolor = self._bgcolor
     685          style._attributes = self._attributes
     686          style._set_attributes = self._set_attributes
     687          style._link = link
     688          style._link_id = f"{randint(0, 999999)}" if link else ""
     689          style._hash = None
     690          style._null = False
     691          style._meta = self._meta
     692          return style
     693  
     694      def render(
     695          self,
     696          text: str = "",
     697          *,
     698          color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
     699          legacy_windows: bool = False,
     700      ) -> str:
     701          """Render the ANSI codes for the style.
     702  
     703          Args:
     704              text (str, optional): A string to style. Defaults to "".
     705              color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
     706  
     707          Returns:
     708              str: A string containing ANSI style codes.
     709          """
     710          if not text or color_system is None:
     711              return text
     712          attrs = self._ansi or self._make_ansi_codes(color_system)
     713          rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
     714          if self._link and not legacy_windows:
     715              rendered = (
     716                  f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
     717              )
     718          return rendered
     719  
     720      def test(self, text: Optional[str] = None) -> None:
     721          """Write text with style directly to terminal.
     722  
     723          This method is for testing purposes only.
     724  
     725          Args:
     726              text (Optional[str], optional): Text to style or None for style name.
     727  
     728          """
     729          text = text or str(self)
     730          sys.stdout.write(f"{self.render(text)}\n")
     731  
     732      @lru_cache(maxsize=1024)
     733      def _add(self, style: Optional["Style"]) -> "Style":
     734          if style is None or style._null:
     735              return self
     736          if self._null:
     737              return style
     738          new_style: Style = self.__new__(Style)
     739          new_style._ansi = None
     740          new_style._style_definition = None
     741          new_style._color = style._color or self._color
     742          new_style._bgcolor = style._bgcolor or self._bgcolor
     743          new_style._attributes = (self._attributes & ~style._set_attributes) | (
     744              style._attributes & style._set_attributes
     745          )
     746          new_style._set_attributes = self._set_attributes | style._set_attributes
     747          new_style._link = style._link or self._link
     748          new_style._link_id = style._link_id or self._link_id
     749          new_style._null = style._null
     750          if self._meta and style._meta:
     751              new_style._meta = dumps({**self.meta, **style.meta})
     752          else:
     753              new_style._meta = self._meta or style._meta
     754          new_style._hash = None
     755          return new_style
     756  
     757      def __add__(self, style: Optional["Style"]) -> "Style":
     758          combined_style = self._add(style)
     759          return combined_style.copy() if combined_style.link else combined_style
     760  
     761  
     762  NULL_STYLE = Style()
     763  
     764  
     765  class ESC[4;38;5;81mStyleStack:
     766      """A stack of styles."""
     767  
     768      __slots__ = ["_stack"]
     769  
     770      def __init__(self, default_style: "Style") -> None:
     771          self._stack: List[Style] = [default_style]
     772  
     773      def __repr__(self) -> str:
     774          return f"<stylestack {self._stack!r}>"
     775  
     776      @property
     777      def current(self) -> Style:
     778          """Get the Style at the top of the stack."""
     779          return self._stack[-1]
     780  
     781      def push(self, style: Style) -> None:
     782          """Push a new style on to the stack.
     783  
     784          Args:
     785              style (Style): New style to combine with current style.
     786          """
     787          self._stack.append(self._stack[-1] + style)
     788  
     789      def pop(self) -> Style:
     790          """Pop last style and discard.
     791  
     792          Returns:
     793              Style: New current style (also available as stack.current)
     794          """
     795          self._stack.pop()
     796          return self._stack[-1]