~divmod-dev/divmod.org/no-addperson-2190

« back to all changes in this revision

Viewing changes to Imaginary/pottery/text.py

  • Committer: exarkun
  • Date: 2006-02-26 02:37:39 UTC
  • Revision ID: svn-v4:866e43f7-fbfc-0310-8f2a-ec88d1da2979:trunk:4991
Merge move-pottery-to-trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: pottery.test.test_text -*-
 
2
 
 
3
import pprint
 
4
 
 
5
from twisted.conch.insults import insults
 
6
 
 
7
class _structlike(list):
 
8
    __names__ = []
 
9
    __slots__ = []
 
10
    __defaults__ = []
 
11
 
 
12
    def _name2slot(self, name):
 
13
        return self.__names__.index(name)
 
14
 
 
15
    def __init__(self, *args, **kw):
 
16
        super(_structlike, self).__init__()
 
17
 
 
18
        # Turn all the args into kwargs
 
19
        for n, v in zip(self.__names__, args):
 
20
            if n in kw:
 
21
                raise TypeError("Got multiple values for argument " + n)
 
22
            kw[n] = v
 
23
 
 
24
        # Fill in defaults
 
25
        for n, v in zip(self.__names__[::-1], self.__defaults__[::-1]):
 
26
            if n not in kw:
 
27
                kw[n] = v
 
28
 
 
29
        self.extend([None] * len(self.__names__))
 
30
        for n in self.__names__:
 
31
            self[self._name2slot(n)] = kw[n]
 
32
 
 
33
    def __getattr__(self, attr):
 
34
        try:
 
35
            return self[self._name2slot(attr)]
 
36
        except (IndexError, ValueError):
 
37
            raise AttributeError(attr)
 
38
 
 
39
    def __setattr__(self, attr, value):
 
40
        try:
 
41
            self[self._name2slot(attr)] = value
 
42
        except ValueError:
 
43
            if attr.startswith('__') and attr.endswith('__'):
 
44
                super(_structlike, self).__setattr__(attr, value)
 
45
            raise AttributeError(attr)
 
46
 
 
47
class _unset(object):
 
48
    def __nonzero__(self):
 
49
        return False
 
50
unset = _unset()
 
51
 
 
52
class AttributeSet(_structlike):
 
53
    """
 
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.
 
57
 
 
58
    @ivar underline: Similar to C{bold} but indicating the underlined
 
59
    state of characters.
 
60
 
 
61
    @ivar reverseVideo: Similar to C{bold} but indicating whether
 
62
    reverse video should be applied.
 
63
 
 
64
    @ivar blink: Similar to C{bold} but indicating whether foreground
 
65
    material should blink.
 
66
 
 
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.
 
71
 
 
72
    @ivar bg: Like C{fg} but for background color.
 
73
    """
 
74
 
 
75
    __names__ = [
 
76
        'bold', 'underline', 'reverseVideo', 'blink',
 
77
        'fg', 'bg']
 
78
 
 
79
    __defaults__ = [
 
80
        False, False, False, False, '9', '9']
 
81
 
 
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)
 
90
 
 
91
    def __repr__(self):
 
92
        return '%s(%s)' % (
 
93
            self.__class__.__name__,
 
94
            ', '.join(['='.join((k, str(v)))
 
95
                       for (k, v)
 
96
                       in zip(self.__names__, self)
 
97
                       if v is not unset]))
 
98
 
 
99
    def clone(self):
 
100
        return self.__class__(*self)
 
101
 
 
102
    def update(self, other):
 
103
        for i in range(len(self)):
 
104
            if other[i] is not unset:
 
105
                self[i] = other[i]
 
106
        return self
 
107
 
 
108
    _flags = {'bold': '1', 'underline': '4', 'reverseVideo': '7', 'blink': '5'}
 
109
    def toVT102(self, state):
 
110
        passive = []
 
111
        active = []
 
112
        reset = False
 
113
 
 
114
        for attr in 'bold', 'underline', 'reverseVideo', 'blink':
 
115
            was = getattr(state, attr)
 
116
            willBe = getattr(self, attr)
 
117
 
 
118
            if was is unset:
 
119
                if willBe is unset:
 
120
                    # Absolutely nothing to do here.
 
121
                    pass
 
122
                elif willBe:
 
123
                    # We're going to turn it on.  Yay.
 
124
                    active.append(self._flags[attr])
 
125
                else:
 
126
                    # We're going to turn it off.  Yay.
 
127
                    reset = True
 
128
            elif was:
 
129
                if willBe is unset:
 
130
                    # Who cares!  But make a note.
 
131
                    passive.append(self._flags[attr])
 
132
                elif willBe:
 
133
                    # Nothing to do!  But make a note.
 
134
                    passive.append(self._flags[attr])
 
135
                else:
 
136
                    # Time to destroy!  Zoom.
 
137
                    reset = True
 
138
            else:
 
139
                if willBe is unset:
 
140
                    # Big woop!  Die.
 
141
                    pass
 
142
                elif willBe:
 
143
                    # Enablement now.
 
144
                    active.append(self._flags[attr])
 
145
                else:
 
146
                    # Consensus is neat.
 
147
                    pass
 
148
 
 
149
        for x, attr in ('3', 'fg'), ('4', 'bg'):
 
150
            was = getattr(state, attr)
 
151
            willBe = getattr(self, attr)
 
152
 
 
153
            if was is unset:
 
154
                if willBe is unset:
 
155
                    # Boringly do nothing.
 
156
                    pass
 
157
                elif willBe == '9':
 
158
                    # Again there is no work.
 
159
                    pass
 
160
                else:
 
161
                    # Wee it is time for colorizing.
 
162
                    active.append(x + willBe)
 
163
            elif was == '9':
 
164
                if willBe is unset:
 
165
                    # We don't care.  Snore.
 
166
                    pass
 
167
                elif willBe == '9':
 
168
                    # We are happily already in the state desired.
 
169
                    pass
 
170
                else:
 
171
                    # Time for mashin'.
 
172
                    active.append(x + willBe)
 
173
            else:
 
174
                if willBe is unset:
 
175
                    # We don't care about the color.
 
176
                    passive.append(x + was)
 
177
                elif willBe == '9':
 
178
                    # Reset the entire state to put this back to normal
 
179
                    reset = True
 
180
                elif willBe == was:
 
181
                    # It is correct already.  Good night.
 
182
                    passive.append(x + was)
 
183
                else:
 
184
                    # Just switch the color
 
185
                    active.append(x + willBe)
 
186
 
 
187
        if reset:
 
188
            active.extend(passive)
 
189
            active.insert(0, '0')
 
190
 
 
191
        if active:
 
192
            return '\x1b[' + ';'.join(active) + 'm'
 
193
        return ''
 
194
 
 
195
 
 
196
class AttributeStack(object):
 
197
    def __init__(self, initialAttributes):
 
198
        self._stack = [initialAttributes]
 
199
 
 
200
    def __repr__(self):
 
201
        return pprint.pformat(self._stack)
 
202
 
 
203
    def __len__(self):
 
204
        return len(self._stack)
 
205
 
 
206
    def push(self, attrs):
 
207
        self._stack.append(self.get().clone().update(attrs))
 
208
 
 
209
    def duptop(self):
 
210
        self._stack.append(self.get().clone())
 
211
 
 
212
    def update(self, attrs):
 
213
        self.get().update(attrs)
 
214
 
 
215
    def pop(self):
 
216
        return self._stack.pop()
 
217
 
 
218
    def get(self):
 
219
        return self._stack[-1]
 
220
 
 
221
class fg:
 
222
    pass
 
223
 
 
224
class bg:
 
225
    pass
 
226
 
 
227
neutral = AttributeSet(unset, unset, unset, unset, unset, unset, unset)
 
228
 
 
229
for cls, attr in [(fg, 'fg'),
 
230
                  (bg, 'bg')]:
 
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
 
239
 
 
240
for attr in 'bold', 'blink', 'reverseVideo', 'underline':
 
241
    value = neutral.clone()
 
242
    setattr(value, attr, True)
 
243
    locals()[attr] = value
 
244
 
 
245
def flatten(*dag, **kw):
 
246
    currentAttrs = kw.pop('currentAttrs')
 
247
    attrs = AttributeStack(currentAttrs)
 
248
    attrs.duptop()
 
249
 
 
250
    if kw:
 
251
        raise TypeError(
 
252
            "flatten takes only `currentAttrs' as a keyword argument")
 
253
 
 
254
    stack = [iter(dag)]
 
255
    dirty = False
 
256
    while stack:
 
257
        try:
 
258
            obj = stack[-1].next()
 
259
        except StopIteration:
 
260
            stack.pop()
 
261
            attrs.pop()
 
262
            if len(attrs):
 
263
                dirty = bool(attrs.get().toVT102(currentAttrs))
 
264
        else:
 
265
            if isinstance(obj, AttributeSet):
 
266
                attrs.update(obj)
 
267
                dirty = bool(attrs.get().toVT102(currentAttrs))
 
268
            elif isinstance(obj, (str, unicode)):
 
269
                if obj:
 
270
                    if dirty:
 
271
                        yield attrs.get().toVT102(currentAttrs)
 
272
                        currentAttrs = attrs.get().clone()
 
273
                        dirty = False
 
274
                    yield obj
 
275
            else:
 
276
                try:
 
277
                    newIter = iter(obj)
 
278
                except TypeError:
 
279
                    if dirty:
 
280
                        yield attrs.get().toVT102(currentAttrs)
 
281
                        currentAttrs = attrs.get().clone()
 
282
                        dirty = False
 
283
                    yield obj
 
284
                else:
 
285
                    stack.append(newIter)
 
286
                    attrs.duptop()
 
287
    if dirty and len(attrs):
 
288
        yield attrs.get().toVT102(currentAttrs)
 
289
 
 
290
__all__ = [
 
291
    'fg', 'bg',
 
292
    'flatten']
 
293
 
 
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'))))