python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_vendor/
rich/
ansi.py
       1  import re
       2  import sys
       3  from contextlib import suppress
       4  from typing import Iterable, NamedTuple, Optional
       5  
       6  from .color import Color
       7  from .style import Style
       8  from .text import Text
       9  
      10  re_ansi = re.compile(
      11      r"""
      12  (?:\x1b\](.*?)\x1b\\)|
      13  (?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
      14  """,
      15      re.VERBOSE,
      16  )
      17  
      18  
      19  class ESC[4;38;5;81m_AnsiToken(ESC[4;38;5;149mNamedTuple):
      20      """Result of ansi tokenized string."""
      21  
      22      plain: str = ""
      23      sgr: Optional[str] = ""
      24      osc: Optional[str] = ""
      25  
      26  
      27  def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
      28      """Tokenize a string in to plain text and ANSI codes.
      29  
      30      Args:
      31          ansi_text (str): A String containing ANSI codes.
      32  
      33      Yields:
      34          AnsiToken: A named tuple of (plain, sgr, osc)
      35      """
      36  
      37      position = 0
      38      sgr: Optional[str]
      39      osc: Optional[str]
      40      for match in re_ansi.finditer(ansi_text):
      41          start, end = match.span(0)
      42          osc, sgr = match.groups()
      43          if start > position:
      44              yield _AnsiToken(ansi_text[position:start])
      45          if sgr:
      46              if sgr == "(":
      47                  position = end + 1
      48                  continue
      49              if sgr.endswith("m"):
      50                  yield _AnsiToken("", sgr[1:-1], osc)
      51          else:
      52              yield _AnsiToken("", sgr, osc)
      53          position = end
      54      if position < len(ansi_text):
      55          yield _AnsiToken(ansi_text[position:])
      56  
      57  
      58  SGR_STYLE_MAP = {
      59      1: "bold",
      60      2: "dim",
      61      3: "italic",
      62      4: "underline",
      63      5: "blink",
      64      6: "blink2",
      65      7: "reverse",
      66      8: "conceal",
      67      9: "strike",
      68      21: "underline2",
      69      22: "not dim not bold",
      70      23: "not italic",
      71      24: "not underline",
      72      25: "not blink",
      73      26: "not blink2",
      74      27: "not reverse",
      75      28: "not conceal",
      76      29: "not strike",
      77      30: "color(0)",
      78      31: "color(1)",
      79      32: "color(2)",
      80      33: "color(3)",
      81      34: "color(4)",
      82      35: "color(5)",
      83      36: "color(6)",
      84      37: "color(7)",
      85      39: "default",
      86      40: "on color(0)",
      87      41: "on color(1)",
      88      42: "on color(2)",
      89      43: "on color(3)",
      90      44: "on color(4)",
      91      45: "on color(5)",
      92      46: "on color(6)",
      93      47: "on color(7)",
      94      49: "on default",
      95      51: "frame",
      96      52: "encircle",
      97      53: "overline",
      98      54: "not frame not encircle",
      99      55: "not overline",
     100      90: "color(8)",
     101      91: "color(9)",
     102      92: "color(10)",
     103      93: "color(11)",
     104      94: "color(12)",
     105      95: "color(13)",
     106      96: "color(14)",
     107      97: "color(15)",
     108      100: "on color(8)",
     109      101: "on color(9)",
     110      102: "on color(10)",
     111      103: "on color(11)",
     112      104: "on color(12)",
     113      105: "on color(13)",
     114      106: "on color(14)",
     115      107: "on color(15)",
     116  }
     117  
     118  
     119  class ESC[4;38;5;81mAnsiDecoder:
     120      """Translate ANSI code in to styled Text."""
     121  
     122      def __init__(self) -> None:
     123          self.style = Style.null()
     124  
     125      def decode(self, terminal_text: str) -> Iterable[Text]:
     126          """Decode ANSI codes in an iterable of lines.
     127  
     128          Args:
     129              lines (Iterable[str]): An iterable of lines of terminal output.
     130  
     131          Yields:
     132              Text: Marked up Text.
     133          """
     134          for line in terminal_text.splitlines():
     135              yield self.decode_line(line)
     136  
     137      def decode_line(self, line: str) -> Text:
     138          """Decode a line containing ansi codes.
     139  
     140          Args:
     141              line (str): A line of terminal output.
     142  
     143          Returns:
     144              Text: A Text instance marked up according to ansi codes.
     145          """
     146          from_ansi = Color.from_ansi
     147          from_rgb = Color.from_rgb
     148          _Style = Style
     149          text = Text()
     150          append = text.append
     151          line = line.rsplit("\r", 1)[-1]
     152          for plain_text, sgr, osc in _ansi_tokenize(line):
     153              if plain_text:
     154                  append(plain_text, self.style or None)
     155              elif osc is not None:
     156                  if osc.startswith("8;"):
     157                      _params, semicolon, link = osc[2:].partition(";")
     158                      if semicolon:
     159                          self.style = self.style.update_link(link or None)
     160              elif sgr is not None:
     161                  # Translate in to semi-colon separated codes
     162                  # Ignore invalid codes, because we want to be lenient
     163                  codes = [
     164                      min(255, int(_code) if _code else 0)
     165                      for _code in sgr.split(";")
     166                      if _code.isdigit() or _code == ""
     167                  ]
     168                  iter_codes = iter(codes)
     169                  for code in iter_codes:
     170                      if code == 0:
     171                          # reset
     172                          self.style = _Style.null()
     173                      elif code in SGR_STYLE_MAP:
     174                          # styles
     175                          self.style += _Style.parse(SGR_STYLE_MAP[code])
     176                      elif code == 38:
     177                          #  Foreground
     178                          with suppress(StopIteration):
     179                              color_type = next(iter_codes)
     180                              if color_type == 5:
     181                                  self.style += _Style.from_color(
     182                                      from_ansi(next(iter_codes))
     183                                  )
     184                              elif color_type == 2:
     185                                  self.style += _Style.from_color(
     186                                      from_rgb(
     187                                          next(iter_codes),
     188                                          next(iter_codes),
     189                                          next(iter_codes),
     190                                      )
     191                                  )
     192                      elif code == 48:
     193                          # Background
     194                          with suppress(StopIteration):
     195                              color_type = next(iter_codes)
     196                              if color_type == 5:
     197                                  self.style += _Style.from_color(
     198                                      None, from_ansi(next(iter_codes))
     199                                  )
     200                              elif color_type == 2:
     201                                  self.style += _Style.from_color(
     202                                      None,
     203                                      from_rgb(
     204                                          next(iter_codes),
     205                                          next(iter_codes),
     206                                          next(iter_codes),
     207                                      ),
     208                                  )
     209  
     210          return text
     211  
     212  
     213  if sys.platform != "win32" and __name__ == "__main__":  # pragma: no cover
     214      import io
     215      import os
     216      import pty
     217      import sys
     218  
     219      decoder = AnsiDecoder()
     220  
     221      stdout = io.BytesIO()
     222  
     223      def read(fd: int) -> bytes:
     224          data = os.read(fd, 1024)
     225          stdout.write(data)
     226          return data
     227  
     228      pty.spawn(sys.argv[1:], read)
     229  
     230      from .console import Console
     231  
     232      console = Console(record=True)
     233  
     234      stdout_result = stdout.getvalue().decode("utf-8")
     235      print(stdout_result)
     236  
     237      for line in decoder.decode(stdout_result):
     238          console.print(line)
     239  
     240      console.save_html("stdout.html")