You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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