1 # -*- test-case-name: twisted.conch.test.test_text -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Character attribute manipulation API
8 This module provides a domain-specific language (using Python syntax)
9 for the creation of text with additional display attributes associated
10 with it. It is intended as an alternative to manually building up
11 strings containing ECMA 48 character attribute control codes. It
12 currently supports foreground and background colors (black, red,
13 green, yellow, blue, magenta, cyan, and white), intensity selection,
14 underlining, blinking and reverse video. Character set selection
17 Character attributes are specified by using two Python operations:
18 attribute lookup and indexing. For example, the string \"Hello
19 world\" with red foreground and all other attributes set to their
20 defaults, assuming the name twisted.conch.insults.text.attributes has
21 been imported and bound to the name \"A\" (with the statement C{from
22 twisted.conch.insults.text import attributes as A}, for example) one
23 uses this expression::
25 | A.fg.red[\"Hello world\"]
27 Other foreground colors are set by substituting their name for
28 \"red\". To set both a foreground and a background color, this
31 | A.fg.red[A.bg.green[\"Hello world\"]]
33 Note that either A.bg.green can be nested within A.fg.red or vice
34 versa. Also note that multiple items can be nested within a single
35 index operation by separating them with commas::
37 | A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]
39 Other character attributes are set in a similar fashion. To specify a
40 blinking version of the previous expression::
42 | A.blink[A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]]
44 C{A.reverseVideo}, C{A.underline}, and C{A.bold} are also valid.
46 A third operation is actually supported: unary negation. This turns
47 off an attribute when an enclosing expression would otherwise have
48 caused it to be on. For example::
50 | A.underline[A.fg.red[\"Hello\", -A.underline[\" world\"]]]
55 from twisted.conch.insults import helper, insults
57 class _Attribute(object):
61 def __getitem__(self, item):
62 assert isinstance(item, (list, tuple, _Attribute, str))
63 if isinstance(item, (list, tuple)):
64 self.children.extend(item)
66 self.children.append(item)
69 def serialize(self, write, attrs=None):
71 attrs = helper.CharacterAttribute()
72 for ch in self.children:
73 if isinstance(ch, _Attribute):
74 ch.serialize(write, attrs.copy())
76 write(attrs.toVT102())
79 class _NormalAttr(_Attribute):
80 def serialize(self, write, attrs):
82 super(_NormalAttr, self).serialize(write, attrs)
84 class _OtherAttr(_Attribute):
85 def __init__(self, attrname, attrvalue):
86 self.attrname = attrname
87 self.attrvalue = attrvalue
91 result = _OtherAttr(self.attrname, not self.attrvalue)
92 result.children.extend(self.children)
95 def serialize(self, write, attrs):
96 attrs = attrs.wantOne(**{self.attrname: self.attrvalue})
97 super(_OtherAttr, self).serialize(write, attrs)
99 class _ColorAttr(_Attribute):
100 def __init__(self, color, ground):
105 def serialize(self, write, attrs):
106 attrs = attrs.wantOne(**{self.ground: self.color})
107 super(_ColorAttr, self).serialize(write, attrs)
109 class _ForegroundColorAttr(_ColorAttr):
110 def __init__(self, color):
111 super(_ForegroundColorAttr, self).__init__(color, 'foreground')
113 class _BackgroundColorAttr(_ColorAttr):
114 def __init__(self, color):
115 super(_BackgroundColorAttr, self).__init__(color, 'background')
117 class CharacterAttributes(object):
118 class _ColorAttribute(object):
119 def __init__(self, ground):
123 'black': helper.BLACK,
125 'green': helper.GREEN,
126 'yellow': helper.YELLOW,
128 'magenta': helper.MAGENTA,
130 'white': helper.WHITE}
132 def __getattr__(self, name):
134 return self.ground(self.attrs[name])
136 raise AttributeError(name)
138 fg = _ColorAttribute(_ForegroundColorAttr)
139 bg = _ColorAttribute(_BackgroundColorAttr)
142 'bold': insults.BOLD,
143 'blink': insults.BLINK,
144 'underline': insults.UNDERLINE,
145 'reverseVideo': insults.REVERSE_VIDEO}
147 def __getattr__(self, name):
150 if name in self.attrs:
151 return _OtherAttr(name, True)
152 raise AttributeError(name)
154 def flatten(output, attrs):
155 """Serialize a sequence of characters with attribute information
157 The resulting string can be interpreted by VT102-compatible
158 terminals so that the contained characters are displayed and, for
159 those attributes which the terminal supports, have the attributes
160 specified in the input.
162 For example, if your terminal is VT102 compatible, you might run
163 this for a colorful variation on the \"hello world\" theme::
165 | from twisted.conch.insults.text import flatten, attributes as A
166 | from twisted.conch.insults.helper import CharacterAttribute
168 | A.normal[A.bold[A.fg.red['He'], A.fg.green['ll'], A.fg.magenta['o'], ' ',
169 | A.fg.yellow['Wo'], A.fg.blue['rl'], A.fg.cyan['d!']]],
170 | CharacterAttribute())
172 @param output: Object returned by accessing attributes of the
173 module-level attributes object.
175 @param attrs: A L{twisted.conch.insults.helper.CharacterAttribute}
178 @return: A VT102-friendly string
181 output.serialize(L.append, attrs)
184 attributes = CharacterAttributes()
186 __all__ = ['attributes', 'flatten']