123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- #
- """Module to parse ANSI escape sequences
- Maintainer: Jean-Paul Calderone
- """
- import string
- # Twisted imports
- from twisted.logger import Logger
- _log = Logger()
- class ColorText:
- """
- Represents an element of text along with the texts colors and
- additional attributes.
- """
- # The colors to use
- COLORS = ("b", "r", "g", "y", "l", "m", "c", "w")
- BOLD_COLORS = tuple(x.upper() for x in COLORS)
- BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(len(COLORS))
- # Color names
- COLOR_NAMES = (
- "Black",
- "Red",
- "Green",
- "Yellow",
- "Blue",
- "Magenta",
- "Cyan",
- "White",
- )
- def __init__(self, text, fg, bg, display, bold, underline, flash, reverse):
- self.text, self.fg, self.bg = text, fg, bg
- self.display = display
- self.bold = bold
- self.underline = underline
- self.flash = flash
- self.reverse = reverse
- if self.reverse:
- self.fg, self.bg = self.bg, self.fg
- class AnsiParser:
- """
- Parser class for ANSI codes.
- """
- # Terminators for cursor movement ansi controls - unsupported
- CURSOR_SET = ("H", "f", "A", "B", "C", "D", "R", "s", "u", "d", "G")
- # Terminators for erasure ansi controls - unsupported
- ERASE_SET = ("J", "K", "P")
- # Terminators for mode change ansi controls - unsupported
- MODE_SET = ("h", "l")
- # Terminators for keyboard assignment ansi controls - unsupported
- ASSIGN_SET = ("p",)
- # Terminators for color change ansi controls - supported
- COLOR_SET = ("m",)
- SETS = (CURSOR_SET, ERASE_SET, MODE_SET, ASSIGN_SET, COLOR_SET)
- def __init__(self, defaultFG, defaultBG):
- self.defaultFG, self.defaultBG = defaultFG, defaultBG
- self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
- self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
- self.display = 1
- self.prepend = ""
- def stripEscapes(self, string):
- """
- Remove all ANSI color escapes from the given string.
- """
- result = ""
- show = 1
- i = 0
- L = len(string)
- while i < L:
- if show == 0 and string[i] in _sets:
- show = 1
- elif show:
- n = string.find("\x1B", i)
- if n == -1:
- return result + string[i:]
- else:
- result = result + string[i:n]
- i = n
- show = 0
- i = i + 1
- return result
- def writeString(self, colorstr):
- pass
- def parseString(self, str):
- """
- Turn a string input into a list of L{ColorText} elements.
- """
- if self.prepend:
- str = self.prepend + str
- self.prepend = ""
- parts = str.split("\x1B")
- if len(parts) == 1:
- self.writeString(self.formatText(parts[0]))
- else:
- self.writeString(self.formatText(parts[0]))
- for s in parts[1:]:
- L = len(s)
- i = 0
- type = None
- while i < L:
- if s[i] not in string.digits + "[;?":
- break
- i += 1
- if not s:
- self.prepend = "\x1b"
- return
- if s[0] != "[":
- self.writeString(self.formatText(s[i + 1 :]))
- continue
- else:
- s = s[1:]
- i -= 1
- if i == L - 1:
- self.prepend = "\x1b["
- return
- type = _setmap.get(s[i], None)
- if type is None:
- continue
- if type == AnsiParser.COLOR_SET:
- self.parseColor(s[: i + 1])
- s = s[i + 1 :]
- self.writeString(self.formatText(s))
- elif type == AnsiParser.CURSOR_SET:
- cursor, s = s[: i + 1], s[i + 1 :]
- self.parseCursor(cursor)
- self.writeString(self.formatText(s))
- elif type == AnsiParser.ERASE_SET:
- erase, s = s[: i + 1], s[i + 1 :]
- self.parseErase(erase)
- self.writeString(self.formatText(s))
- elif type == AnsiParser.MODE_SET:
- s = s[i + 1 :]
- # self.parseErase('2J')
- self.writeString(self.formatText(s))
- elif i == L:
- self.prepend = "\x1B[" + s
- else:
- _log.warn(
- "Unhandled ANSI control type: {control_type}", control_type=s[i]
- )
- s = s[i + 1 :]
- self.writeString(self.formatText(s))
- def parseColor(self, str):
- """
- Handle a single ANSI color sequence
- """
- # Drop the trailing 'm'
- str = str[:-1]
- if not str:
- str = "0"
- try:
- parts = map(int, str.split(";"))
- except ValueError:
- _log.error("Invalid ANSI color sequence: {sequence!r}", sequence=str)
- self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
- return
- for x in parts:
- if x == 0:
- self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
- self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
- self.display = 1
- elif x == 1:
- self.bold = 1
- elif 30 <= x <= 37:
- self.currentFG = x - 30
- elif 40 <= x <= 47:
- self.currentBG = x - 40
- elif x == 39:
- self.currentFG = self.defaultFG
- elif x == 49:
- self.currentBG = self.defaultBG
- elif x == 4:
- self.underline = 1
- elif x == 5:
- self.flash = 1
- elif x == 7:
- self.reverse = 1
- elif x == 8:
- self.display = 0
- elif x == 22:
- self.bold = 0
- elif x == 24:
- self.underline = 0
- elif x == 25:
- self.blink = 0
- elif x == 27:
- self.reverse = 0
- elif x == 28:
- self.display = 1
- else:
- _log.error("Unrecognised ANSI color command: {command}", command=x)
- def parseCursor(self, cursor):
- pass
- def parseErase(self, erase):
- pass
- def pickColor(self, value, mode, BOLD=ColorText.BOLD_COLORS):
- if mode:
- return ColorText.COLORS[value]
- else:
- return self.bold and BOLD[value] or ColorText.COLORS[value]
- def formatText(self, text):
- return ColorText(
- text,
- self.pickColor(self.currentFG, 0),
- self.pickColor(self.currentBG, 1),
- self.display,
- self.bold,
- self.underline,
- self.flash,
- self.reverse,
- )
- _sets = "".join(map("".join, AnsiParser.SETS))
- _setmap = {}
- for s in AnsiParser.SETS:
- for r in s:
- _setmap[r] = s
- del s
|