ansi.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. #
  4. """Module to parse ANSI escape sequences
  5. Maintainer: Jean-Paul Calderone
  6. """
  7. import string
  8. # Twisted imports
  9. from twisted.logger import Logger
  10. _log = Logger()
  11. class ColorText:
  12. """
  13. Represents an element of text along with the texts colors and
  14. additional attributes.
  15. """
  16. # The colors to use
  17. COLORS = ("b", "r", "g", "y", "l", "m", "c", "w")
  18. BOLD_COLORS = tuple(x.upper() for x in COLORS)
  19. BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(len(COLORS))
  20. # Color names
  21. COLOR_NAMES = (
  22. "Black",
  23. "Red",
  24. "Green",
  25. "Yellow",
  26. "Blue",
  27. "Magenta",
  28. "Cyan",
  29. "White",
  30. )
  31. def __init__(self, text, fg, bg, display, bold, underline, flash, reverse):
  32. self.text, self.fg, self.bg = text, fg, bg
  33. self.display = display
  34. self.bold = bold
  35. self.underline = underline
  36. self.flash = flash
  37. self.reverse = reverse
  38. if self.reverse:
  39. self.fg, self.bg = self.bg, self.fg
  40. class AnsiParser:
  41. """
  42. Parser class for ANSI codes.
  43. """
  44. # Terminators for cursor movement ansi controls - unsupported
  45. CURSOR_SET = ("H", "f", "A", "B", "C", "D", "R", "s", "u", "d", "G")
  46. # Terminators for erasure ansi controls - unsupported
  47. ERASE_SET = ("J", "K", "P")
  48. # Terminators for mode change ansi controls - unsupported
  49. MODE_SET = ("h", "l")
  50. # Terminators for keyboard assignment ansi controls - unsupported
  51. ASSIGN_SET = ("p",)
  52. # Terminators for color change ansi controls - supported
  53. COLOR_SET = ("m",)
  54. SETS = (CURSOR_SET, ERASE_SET, MODE_SET, ASSIGN_SET, COLOR_SET)
  55. def __init__(self, defaultFG, defaultBG):
  56. self.defaultFG, self.defaultBG = defaultFG, defaultBG
  57. self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
  58. self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
  59. self.display = 1
  60. self.prepend = ""
  61. def stripEscapes(self, string):
  62. """
  63. Remove all ANSI color escapes from the given string.
  64. """
  65. result = ""
  66. show = 1
  67. i = 0
  68. L = len(string)
  69. while i < L:
  70. if show == 0 and string[i] in _sets:
  71. show = 1
  72. elif show:
  73. n = string.find("\x1B", i)
  74. if n == -1:
  75. return result + string[i:]
  76. else:
  77. result = result + string[i:n]
  78. i = n
  79. show = 0
  80. i = i + 1
  81. return result
  82. def writeString(self, colorstr):
  83. pass
  84. def parseString(self, str):
  85. """
  86. Turn a string input into a list of L{ColorText} elements.
  87. """
  88. if self.prepend:
  89. str = self.prepend + str
  90. self.prepend = ""
  91. parts = str.split("\x1B")
  92. if len(parts) == 1:
  93. self.writeString(self.formatText(parts[0]))
  94. else:
  95. self.writeString(self.formatText(parts[0]))
  96. for s in parts[1:]:
  97. L = len(s)
  98. i = 0
  99. type = None
  100. while i < L:
  101. if s[i] not in string.digits + "[;?":
  102. break
  103. i += 1
  104. if not s:
  105. self.prepend = "\x1b"
  106. return
  107. if s[0] != "[":
  108. self.writeString(self.formatText(s[i + 1 :]))
  109. continue
  110. else:
  111. s = s[1:]
  112. i -= 1
  113. if i == L - 1:
  114. self.prepend = "\x1b["
  115. return
  116. type = _setmap.get(s[i], None)
  117. if type is None:
  118. continue
  119. if type == AnsiParser.COLOR_SET:
  120. self.parseColor(s[: i + 1])
  121. s = s[i + 1 :]
  122. self.writeString(self.formatText(s))
  123. elif type == AnsiParser.CURSOR_SET:
  124. cursor, s = s[: i + 1], s[i + 1 :]
  125. self.parseCursor(cursor)
  126. self.writeString(self.formatText(s))
  127. elif type == AnsiParser.ERASE_SET:
  128. erase, s = s[: i + 1], s[i + 1 :]
  129. self.parseErase(erase)
  130. self.writeString(self.formatText(s))
  131. elif type == AnsiParser.MODE_SET:
  132. s = s[i + 1 :]
  133. # self.parseErase('2J')
  134. self.writeString(self.formatText(s))
  135. elif i == L:
  136. self.prepend = "\x1B[" + s
  137. else:
  138. _log.warn(
  139. "Unhandled ANSI control type: {control_type}", control_type=s[i]
  140. )
  141. s = s[i + 1 :]
  142. self.writeString(self.formatText(s))
  143. def parseColor(self, str):
  144. """
  145. Handle a single ANSI color sequence
  146. """
  147. # Drop the trailing 'm'
  148. str = str[:-1]
  149. if not str:
  150. str = "0"
  151. try:
  152. parts = map(int, str.split(";"))
  153. except ValueError:
  154. _log.error("Invalid ANSI color sequence: {sequence!r}", sequence=str)
  155. self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
  156. return
  157. for x in parts:
  158. if x == 0:
  159. self.currentFG, self.currentBG = self.defaultFG, self.defaultBG
  160. self.bold, self.flash, self.underline, self.reverse = 0, 0, 0, 0
  161. self.display = 1
  162. elif x == 1:
  163. self.bold = 1
  164. elif 30 <= x <= 37:
  165. self.currentFG = x - 30
  166. elif 40 <= x <= 47:
  167. self.currentBG = x - 40
  168. elif x == 39:
  169. self.currentFG = self.defaultFG
  170. elif x == 49:
  171. self.currentBG = self.defaultBG
  172. elif x == 4:
  173. self.underline = 1
  174. elif x == 5:
  175. self.flash = 1
  176. elif x == 7:
  177. self.reverse = 1
  178. elif x == 8:
  179. self.display = 0
  180. elif x == 22:
  181. self.bold = 0
  182. elif x == 24:
  183. self.underline = 0
  184. elif x == 25:
  185. self.blink = 0
  186. elif x == 27:
  187. self.reverse = 0
  188. elif x == 28:
  189. self.display = 1
  190. else:
  191. _log.error("Unrecognised ANSI color command: {command}", command=x)
  192. def parseCursor(self, cursor):
  193. pass
  194. def parseErase(self, erase):
  195. pass
  196. def pickColor(self, value, mode, BOLD=ColorText.BOLD_COLORS):
  197. if mode:
  198. return ColorText.COLORS[value]
  199. else:
  200. return self.bold and BOLD[value] or ColorText.COLORS[value]
  201. def formatText(self, text):
  202. return ColorText(
  203. text,
  204. self.pickColor(self.currentFG, 0),
  205. self.pickColor(self.currentBG, 1),
  206. self.display,
  207. self.bold,
  208. self.underline,
  209. self.flash,
  210. self.reverse,
  211. )
  212. _sets = "".join(map("".join, AnsiParser.SETS))
  213. _setmap = {}
  214. for s in AnsiParser.SETS:
  215. for r in s:
  216. _setmap[r] = s
  217. del s