root/trunk/twisted/conch/ui/ansi.py

Revision 30752, 7.1 KB (checked in by exarkun, 15 months ago)

Rewrite the copyright headers to exclude date information.

Author: exarkun
Reviewer: glyph
Fixes: #4857

To avoid the need to perpetually update copyright dates in each file in Twisted,
remove the dates from most files and just leave them in the LICENSE file.

As a side effect, some files also have had a trailing newline added where it was
missing before.

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