1
# -*- test-case-name: pottery.test.test_text -*-
5
from twisted.conch.insults import insults
7
class _structlike(list):
12
def _name2slot(self, name):
13
return self.__names__.index(name)
15
def __init__(self, *args, **kw):
16
super(_structlike, self).__init__()
18
# Turn all the args into kwargs
19
for n, v in zip(self.__names__, args):
21
raise TypeError("Got multiple values for argument " + n)
25
for n, v in zip(self.__names__[::-1], self.__defaults__[::-1]):
29
self.extend([None] * len(self.__names__))
30
for n in self.__names__:
31
self[self._name2slot(n)] = kw[n]
33
def __getattr__(self, attr):
35
return self[self._name2slot(attr)]
36
except (IndexError, ValueError):
37
raise AttributeError(attr)
39
def __setattr__(self, attr, value):
41
self[self._name2slot(attr)] = value
43
if attr.startswith('__') and attr.endswith('__'):
44
super(_structlike, self).__setattr__(attr, value)
45
raise AttributeError(attr)
48
def __nonzero__(self):
52
class AttributeSet(_structlike):
54
@ivar bold: True, False, or unset, indicating whether characters
55
with these attributes will be bold, or if boldness should be
56
inherited from the previous setting.
58
@ivar underline: Similar to C{bold} but indicating the underlined
61
@ivar reverseVideo: Similar to C{bold} but indicating whether
62
reverse video should be applied.
64
@ivar blink: Similar to C{bold} but indicating whether foreground
65
material should blink.
67
@ivar fg: An integer between 0 and 9 inclusive or unset.
68
Integer values indicate a color setting for the foreground,
69
whereas unset indicates foreground color should be inherited from
70
the previous settings.
72
@ivar bg: Like C{fg} but for background color.
76
'bold', 'underline', 'reverseVideo', 'blink',
80
False, False, False, False, '9', '9']
82
def __init__(self, *a, **kw):
83
_structlike.__init__(self, *a, **kw)
84
assert self.fg is unset or self.fg in '012345679'
85
assert self.bg is unset or self.bg in '012345679'
86
assert self.bold is unset or self.bold in (True, False)
87
assert self.underline is unset or self.underline in (True, False)
88
assert self.reverseVideo is unset or self.reverseVideo in (True, False)
89
assert self.blink is unset or self.blink in (True, False)
93
self.__class__.__name__,
94
', '.join(['='.join((k, str(v)))
96
in zip(self.__names__, self)
100
return self.__class__(*self)
102
def update(self, other):
103
for i in range(len(self)):
104
if other[i] is not unset:
108
_flags = {'bold': '1', 'underline': '4', 'reverseVideo': '7', 'blink': '5'}
109
def toVT102(self, state):
114
for attr in 'bold', 'underline', 'reverseVideo', 'blink':
115
was = getattr(state, attr)
116
willBe = getattr(self, attr)
120
# Absolutely nothing to do here.
123
# We're going to turn it on. Yay.
124
active.append(self._flags[attr])
126
# We're going to turn it off. Yay.
130
# Who cares! But make a note.
131
passive.append(self._flags[attr])
133
# Nothing to do! But make a note.
134
passive.append(self._flags[attr])
136
# Time to destroy! Zoom.
144
active.append(self._flags[attr])
149
for x, attr in ('3', 'fg'), ('4', 'bg'):
150
was = getattr(state, attr)
151
willBe = getattr(self, attr)
155
# Boringly do nothing.
158
# Again there is no work.
161
# Wee it is time for colorizing.
162
active.append(x + willBe)
165
# We don't care. Snore.
168
# We are happily already in the state desired.
172
active.append(x + willBe)
175
# We don't care about the color.
176
passive.append(x + was)
178
# Reset the entire state to put this back to normal
181
# It is correct already. Good night.
182
passive.append(x + was)
184
# Just switch the color
185
active.append(x + willBe)
188
active.extend(passive)
189
active.insert(0, '0')
192
return '\x1b[' + ';'.join(active) + 'm'
196
class AttributeStack(object):
197
def __init__(self, initialAttributes):
198
self._stack = [initialAttributes]
201
return pprint.pformat(self._stack)
204
return len(self._stack)
206
def push(self, attrs):
207
self._stack.append(self.get().clone().update(attrs))
210
self._stack.append(self.get().clone())
212
def update(self, attrs):
213
self.get().update(attrs)
216
return self._stack.pop()
219
return self._stack[-1]
227
neutral = AttributeSet(unset, unset, unset, unset, unset, unset, unset)
229
for cls, attr in [(fg, 'fg'),
231
for n, color in enumerate(['black', 'red', 'green', 'yellow', 'blue',
232
'magenta', 'cyan', 'white']):
233
value = neutral.clone()
234
setattr(value, attr, str(n))
235
setattr(cls, color, value)
236
cls.normal = neutral.clone()
237
setattr(cls.normal, attr, '9')
238
del n, cls, attr, color
240
for attr in 'bold', 'blink', 'reverseVideo', 'underline':
241
value = neutral.clone()
242
setattr(value, attr, True)
243
locals()[attr] = value
245
def flatten(*dag, **kw):
246
currentAttrs = kw.pop('currentAttrs')
247
attrs = AttributeStack(currentAttrs)
252
"flatten takes only `currentAttrs' as a keyword argument")
258
obj = stack[-1].next()
259
except StopIteration:
263
dirty = bool(attrs.get().toVT102(currentAttrs))
265
if isinstance(obj, AttributeSet):
267
dirty = bool(attrs.get().toVT102(currentAttrs))
268
elif isinstance(obj, (str, unicode)):
271
yield attrs.get().toVT102(currentAttrs)
272
currentAttrs = attrs.get().clone()
280
yield attrs.get().toVT102(currentAttrs)
281
currentAttrs = attrs.get().clone()
285
stack.append(newIter)
287
if dirty and len(attrs):
288
yield attrs.get().toVT102(currentAttrs)
294
if __name__ == '__main__':
295
print repr(''.join(list(flatten(
296
[fg.red, bg.blue, bold, ['hello world']], '\n',
297
[fg.blue, bg.red, blink, ['how are you?']], '\n',
298
[fg.blue, [bg.red, [blink, 'how'], ' are'], ' you?'], '\n'))))