1
# -*- test-case-name: imaginary.test.test_text -*-
5
from epsilon import structlike
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')):
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.
20
@ivar underline: Similar to C{bold} but indicating the underlined
23
@ivar reverseVideo: Similar to C{bold} but indicating whether
24
reverse video should be applied.
26
@ivar blink: Similar to C{bold} but indicating whether foreground
27
material should blink.
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.
34
@ivar bg: Like C{fg} but for background color.
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)
48
self.__class__.__name__,
49
', '.join(['='.join((k, str(v)))
51
in zip(self.__names__, self)
59
def __getitem__(self, index):
60
return [self.bold, self.underline, self.reverseVideo, self.blink, self.fg, self.bg][index]
63
def __setitem__(self, index, value):
64
setattr(self, ['bold', 'underline', 'reverseVideo', 'blink', 'fg', 'bg'][index], value)
68
return self.__class__(*self)
70
def update(self, other):
71
for i in range(len(self)):
72
if other[i] is not unset:
76
_flags = {'bold': '1', 'underline': '4', 'reverseVideo': '7', 'blink': '5'}
77
def toVT102(self, state):
82
for attr in 'bold', 'underline', 'reverseVideo', 'blink':
83
was = getattr(state, attr)
84
willBe = getattr(self, attr)
88
# Absolutely nothing to do here.
91
# We're going to turn it on. Yay.
92
active.append(self._flags[attr])
94
# We're going to turn it off. Yay.
98
# Who cares! But make a note.
99
passive.append(self._flags[attr])
101
# Nothing to do! But make a note.
102
passive.append(self._flags[attr])
104
# Time to destroy! Zoom.
112
active.append(self._flags[attr])
117
for x, attr in ('3', 'fg'), ('4', 'bg'):
118
was = getattr(state, attr)
119
willBe = getattr(self, attr)
123
# Boringly do nothing.
126
# Again there is no work.
129
# Wee it is time for colorizing.
130
active.append(x + willBe)
133
# We don't care. Snore.
136
# We are happily already in the state desired.
140
active.append(x + willBe)
143
# We don't care about the color.
144
passive.append(x + was)
146
# Reset the entire state to put this back to normal
149
# It is correct already. Good night.
150
passive.append(x + was)
152
# Just switch the color
153
active.append(x + willBe)
156
active.extend(passive)
157
active.insert(0, '0')
160
return '\x1b[' + ';'.join(active) + 'm'
164
class AttributeStack(object):
165
def __init__(self, initialAttributes):
166
self._stack = [initialAttributes]
169
return pprint.pformat(self._stack)
172
return len(self._stack)
174
def push(self, attrs):
175
self._stack.append(self.get().clone().update(attrs))
178
self._stack.append(self.get().clone())
180
def update(self, attrs):
181
self.get().update(attrs)
184
return self._stack.pop()
187
return self._stack[-1]
195
neutral = AttributeSet(unset, unset, unset, unset, unset, unset)
197
for cls, attr in [(fg, 'fg'),
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
208
for attr in 'bold', 'blink', 'reverseVideo', 'underline':
209
value = neutral.clone()
210
setattr(value, attr, True)
211
locals()[attr] = value
213
def flatten(dag, currentAttrs=None, useColors=True):
214
# XXX TODO: Add unicode handling!
216
Serialize a tree of strings and terminal codes to an iterable of strings,
217
ready to be written to your favorite terminal device.
219
@type currentAttrs: L{AttributeSet}
220
@param currentAttrs: The current set of attributes.
222
@param useColors: If False, terminal codes will be left out.
224
if currentAttrs is None:
226
currentAttrs = AttributeSet()
228
raise TypeError("currentAttrs is required when useColors is False")
229
attrs = AttributeStack(currentAttrs)
236
obj = stack[-1].next()
237
except StopIteration:
242
dirty = bool(attrs.get().toVT102(currentAttrs))
244
if isinstance(obj, AttributeSet):
247
dirty = bool(attrs.get().toVT102(currentAttrs))
248
elif isinstance(obj, (str, unicode)):
252
yield attrs.get().toVT102(currentAttrs)
253
currentAttrs = attrs.get().clone()
262
yield attrs.get().toVT102(currentAttrs)
263
currentAttrs = attrs.get().clone()
267
stack.append(newIter)
269
if dirty and len(attrs):
271
yield attrs.get().toVT102(currentAttrs)