~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Imaginary/imaginary/text.py

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-29 20:33:04 UTC
  • mfrom: (2749.1.1 remove-epsilon-1325289)
  • Revision ID: exarkun@twistedmatrix.com-20140629203304-gdkmbwl1suei4m97
mergeĀ lp:~exarkun/divmod.org/remove-epsilon-1325289

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: imaginary.test.test_text -*-
2
 
 
3
 
import pprint
4
 
 
5
 
from epsilon import structlike
6
 
 
7
 
class _unset(object):
8
 
    def __nonzero__(self):
9
 
        return False
10
 
unset = _unset()
11
 
 
12
 
class AttributeSet(structlike.record('bold underline reverseVideo blink fg bg',
13
 
                                     bold=False, underline=False, reverseVideo=False,
14
 
                                     blink=False, fg='9', bg='9')):
15
 
    """
16
 
    @ivar bold: True, False, or unset, indicating whether characters
17
 
    with these attributes will be bold, or if boldness should be
18
 
    inherited from the previous setting.
19
 
 
20
 
    @ivar underline: Similar to C{bold} but indicating the underlined
21
 
    state of characters.
22
 
 
23
 
    @ivar reverseVideo: Similar to C{bold} but indicating whether
24
 
    reverse video should be applied.
25
 
 
26
 
    @ivar blink: Similar to C{bold} but indicating whether foreground
27
 
    material should blink.
28
 
 
29
 
    @ivar fg: An integer between 0 and 9 inclusive or unset.
30
 
    Integer values indicate a color setting for the foreground,
31
 
    whereas unset indicates foreground color should be inherited from
32
 
    the previous settings.
33
 
 
34
 
    @ivar bg: Like C{fg} but for background color.
35
 
    """
36
 
 
37
 
    def __init__(self, *a, **kw):
38
 
        super(AttributeSet, self).__init__(*a, **kw)
39
 
        assert self.fg is unset or self.fg in '012345679'
40
 
        assert self.bg is unset or self.bg in '012345679'
41
 
        assert self.bold is unset or self.bold in (True, False)
42
 
        assert self.underline is unset or self.underline in (True, False)
43
 
        assert self.reverseVideo is unset or self.reverseVideo in (True, False)
44
 
        assert self.blink is unset or self.blink in (True, False)
45
 
 
46
 
    def __repr__(self):
47
 
        return '%s(%s)' % (
48
 
            self.__class__.__name__,
49
 
            ', '.join(['='.join((k, str(v)))
50
 
                       for (k, v)
51
 
                       in zip(self.__names__, self)
52
 
                       if v is not unset]))
53
 
 
54
 
 
55
 
    def __len__(self):
56
 
        return 6
57
 
 
58
 
 
59
 
    def __getitem__(self, index):
60
 
        return [self.bold, self.underline, self.reverseVideo, self.blink, self.fg, self.bg][index]
61
 
 
62
 
 
63
 
    def __setitem__(self, index, value):
64
 
        setattr(self, ['bold', 'underline', 'reverseVideo', 'blink', 'fg', 'bg'][index], value)
65
 
 
66
 
 
67
 
    def clone(self):
68
 
        return self.__class__(*self)
69
 
 
70
 
    def update(self, other):
71
 
        for i in range(len(self)):
72
 
            if other[i] is not unset:
73
 
                self[i] = other[i]
74
 
        return self
75
 
 
76
 
    _flags = {'bold': '1', 'underline': '4', 'reverseVideo': '7', 'blink': '5'}
77
 
    def toVT102(self, state):
78
 
        passive = []
79
 
        active = []
80
 
        reset = False
81
 
 
82
 
        for attr in 'bold', 'underline', 'reverseVideo', 'blink':
83
 
            was = getattr(state, attr)
84
 
            willBe = getattr(self, attr)
85
 
 
86
 
            if was is unset:
87
 
                if willBe is unset:
88
 
                    # Absolutely nothing to do here.
89
 
                    pass
90
 
                elif willBe:
91
 
                    # We're going to turn it on.  Yay.
92
 
                    active.append(self._flags[attr])
93
 
                else:
94
 
                    # We're going to turn it off.  Yay.
95
 
                    reset = True
96
 
            elif was:
97
 
                if willBe is unset:
98
 
                    # Who cares!  But make a note.
99
 
                    passive.append(self._flags[attr])
100
 
                elif willBe:
101
 
                    # Nothing to do!  But make a note.
102
 
                    passive.append(self._flags[attr])
103
 
                else:
104
 
                    # Time to destroy!  Zoom.
105
 
                    reset = True
106
 
            else:
107
 
                if willBe is unset:
108
 
                    # Big woop!  Die.
109
 
                    pass
110
 
                elif willBe:
111
 
                    # Enablement now.
112
 
                    active.append(self._flags[attr])
113
 
                else:
114
 
                    # Consensus is neat.
115
 
                    pass
116
 
 
117
 
        for x, attr in ('3', 'fg'), ('4', 'bg'):
118
 
            was = getattr(state, attr)
119
 
            willBe = getattr(self, attr)
120
 
 
121
 
            if was is unset:
122
 
                if willBe is unset:
123
 
                    # Boringly do nothing.
124
 
                    pass
125
 
                elif willBe == '9':
126
 
                    # Again there is no work.
127
 
                    pass
128
 
                else:
129
 
                    # Wee it is time for colorizing.
130
 
                    active.append(x + willBe)
131
 
            elif was == '9':
132
 
                if willBe is unset:
133
 
                    # We don't care.  Snore.
134
 
                    pass
135
 
                elif willBe == '9':
136
 
                    # We are happily already in the state desired.
137
 
                    pass
138
 
                else:
139
 
                    # Time for mashin'.
140
 
                    active.append(x + willBe)
141
 
            else:
142
 
                if willBe is unset:
143
 
                    # We don't care about the color.
144
 
                    passive.append(x + was)
145
 
                elif willBe == '9':
146
 
                    # Reset the entire state to put this back to normal
147
 
                    reset = True
148
 
                elif willBe == was:
149
 
                    # It is correct already.  Good night.
150
 
                    passive.append(x + was)
151
 
                else:
152
 
                    # Just switch the color
153
 
                    active.append(x + willBe)
154
 
 
155
 
        if reset:
156
 
            active.extend(passive)
157
 
            active.insert(0, '0')
158
 
 
159
 
        if active:
160
 
            return '\x1b[' + ';'.join(active) + 'm'
161
 
        return ''
162
 
 
163
 
 
164
 
class AttributeStack(object):
165
 
    def __init__(self, initialAttributes):
166
 
        self._stack = [initialAttributes]
167
 
 
168
 
    def __repr__(self):
169
 
        return pprint.pformat(self._stack)
170
 
 
171
 
    def __len__(self):
172
 
        return len(self._stack)
173
 
 
174
 
    def push(self, attrs):
175
 
        self._stack.append(self.get().clone().update(attrs))
176
 
 
177
 
    def duptop(self):
178
 
        self._stack.append(self.get().clone())
179
 
 
180
 
    def update(self, attrs):
181
 
        self.get().update(attrs)
182
 
 
183
 
    def pop(self):
184
 
        return self._stack.pop()
185
 
 
186
 
    def get(self):
187
 
        return self._stack[-1]
188
 
 
189
 
class fg:
190
 
    pass
191
 
 
192
 
class bg:
193
 
    pass
194
 
 
195
 
neutral = AttributeSet(unset, unset, unset, unset, unset, unset)
196
 
 
197
 
for cls, attr in [(fg, 'fg'),
198
 
                  (bg, 'bg')]:
199
 
    for n, color in enumerate(['black', 'red', 'green', 'yellow', 'blue',
200
 
                               'magenta', 'cyan', 'white']):
201
 
        value = neutral.clone()
202
 
        setattr(value, attr, str(n))
203
 
        setattr(cls, color, value)
204
 
    cls.normal = neutral.clone()
205
 
    setattr(cls.normal, attr, '9')
206
 
del n, cls, attr, color
207
 
 
208
 
for attr in 'bold', 'blink', 'reverseVideo', 'underline':
209
 
    value = neutral.clone()
210
 
    setattr(value, attr, True)
211
 
    locals()[attr] = value
212
 
 
213
 
def flatten(dag, currentAttrs=None, useColors=True):
214
 
    # XXX TODO: Add unicode handling!
215
 
    """
216
 
    Serialize a tree of strings and terminal codes to an iterable of strings,
217
 
    ready to be written to your favorite terminal device.
218
 
 
219
 
    @type currentAttrs: L{AttributeSet}
220
 
    @param currentAttrs: The current set of attributes.
221
 
 
222
 
    @param useColors: If False, terminal codes will be left out.
223
 
    """
224
 
    if currentAttrs is None:
225
 
        if not useColors:
226
 
            currentAttrs = AttributeSet()
227
 
        else:
228
 
            raise TypeError("currentAttrs is required when useColors is False")
229
 
    attrs = AttributeStack(currentAttrs)
230
 
    attrs.duptop()
231
 
 
232
 
    stack = [iter(dag)]
233
 
    dirty = False
234
 
    while stack:
235
 
        try:
236
 
            obj = stack[-1].next()
237
 
        except StopIteration:
238
 
            stack.pop()
239
 
            attrs.pop()
240
 
            if len(attrs):
241
 
                if useColors:
242
 
                    dirty = bool(attrs.get().toVT102(currentAttrs))
243
 
        else:
244
 
            if isinstance(obj, AttributeSet):
245
 
                attrs.update(obj)
246
 
                if useColors:
247
 
                    dirty = bool(attrs.get().toVT102(currentAttrs))
248
 
            elif isinstance(obj, (str, unicode)):
249
 
                if obj:
250
 
                    if dirty:
251
 
                        if useColors:
252
 
                            yield attrs.get().toVT102(currentAttrs)
253
 
                        currentAttrs = attrs.get().clone()
254
 
                        dirty = False
255
 
                    yield obj
256
 
            else:
257
 
                try:
258
 
                    newIter = iter(obj)
259
 
                except TypeError:
260
 
                    if dirty:
261
 
                        if useColors:
262
 
                            yield attrs.get().toVT102(currentAttrs)
263
 
                        currentAttrs = attrs.get().clone()
264
 
                        dirty = False
265
 
                    yield obj
266
 
                else:
267
 
                    stack.append(newIter)
268
 
                    attrs.duptop()
269
 
    if dirty and len(attrs):
270
 
        if useColors:
271
 
            yield attrs.get().toVT102(currentAttrs)
272
 
 
273
 
 
274
 
 
275
 
__all__ = [
276
 
    'fg', 'bg',
277
 
    'flatten']