| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.conch.test.test_text -*- | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 """ | |
| 6 Character attribute manipulation API | |
| 7 | |
| 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 | |
| 15 support is planned. | |
| 16 | |
| 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:: | |
| 24 | |
| 25 | A.fg.red[\"Hello world\"] | |
| 26 | |
| 27 Other foreground colors are set by substituting their name for | |
| 28 \"red\". To set both a foreground and a background color, this | |
| 29 expression is used:: | |
| 30 | |
| 31 | A.fg.red[A.bg.green[\"Hello world\"]] | |
| 32 | |
| 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:: | |
| 36 | |
| 37 | A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]] | |
| 38 | |
| 39 Other character attributes are set in a similar fashion. To specify a | |
| 40 blinking version of the previous expression:: | |
| 41 | |
| 42 | A.blink[A.bg.green[A.fg.red[\"Hello\"], " ", A.fg.blue[\"world\"]]] | |
| 43 | |
| 44 C{A.reverseVideo}, C{A.underline}, and C{A.bold} are also valid. | |
| 45 | |
| 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:: | |
| 49 | |
| 50 | A.underline[A.fg.red[\"Hello\", -A.underline[\" world\"]]] | |
| 51 | |
| 52 @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | |
| 53 """ | |
| 54 | |
| 55 from twisted.conch.insults import helper, insults | |
| 56 | |
| 57 class _Attribute(object): | |
| 58 def __init__(self): | |
| 59 self.children = [] | |
| 60 | |
| 61 def __getitem__(self, item): | |
| 62 assert isinstance(item, (list, tuple, _Attribute, str)) | |
| 63 if isinstance(item, (list, tuple)): | |
| 64 self.children.extend(item) | |
| 65 else: | |
| 66 self.children.append(item) | |
| 67 return self | |
| 68 | |
| 69 def serialize(self, write, attrs=None): | |
| 70 if attrs is None: | |
| 71 attrs = helper.CharacterAttribute() | |
| 72 for ch in self.children: | |
| 73 if isinstance(ch, _Attribute): | |
| 74 ch.serialize(write, attrs.copy()) | |
| 75 else: | |
| 76 write(attrs.toVT102()) | |
| 77 write(ch) | |
| 78 | |
| 79 class _NormalAttr(_Attribute): | |
| 80 def serialize(self, write, attrs): | |
| 81 attrs.__init__() | |
| 82 super(_NormalAttr, self).serialize(write, attrs) | |
| 83 | |
| 84 class _OtherAttr(_Attribute): | |
| 85 def __init__(self, attrname, attrvalue): | |
| 86 self.attrname = attrname | |
| 87 self.attrvalue = attrvalue | |
| 88 self.children = [] | |
| 89 | |
| 90 def __neg__(self): | |
| 91 result = _OtherAttr(self.attrname, not self.attrvalue) | |
| 92 result.children.extend(self.children) | |
| 93 return result | |
| 94 | |
| 95 def serialize(self, write, attrs): | |
| 96 attrs = attrs.wantOne(**{self.attrname: self.attrvalue}) | |
| 97 super(_OtherAttr, self).serialize(write, attrs) | |
| 98 | |
| 99 class _ColorAttr(_Attribute): | |
| 100 def __init__(self, color, ground): | |
| 101 self.color = color | |
| 102 self.ground = ground | |
| 103 self.children = [] | |
| 104 | |
| 105 def serialize(self, write, attrs): | |
| 106 attrs = attrs.wantOne(**{self.ground: self.color}) | |
| 107 super(_ColorAttr, self).serialize(write, attrs) | |
| 108 | |
| 109 class _ForegroundColorAttr(_ColorAttr): | |
| 110 def __init__(self, color): | |
| 111 super(_ForegroundColorAttr, self).__init__(color, 'foreground') | |
| 112 | |
| 113 class _BackgroundColorAttr(_ColorAttr): | |
| 114 def __init__(self, color): | |
| 115 super(_BackgroundColorAttr, self).__init__(color, 'background') | |
| 116 | |
| 117 class CharacterAttributes(object): | |
| 118 class _ColorAttribute(object): | |
| 119 def __init__(self, ground): | |
| 120 self.ground = ground | |
| 121 | |
| 122 attrs = { | |
| 123 'black': helper.BLACK, | |
| 124 'red': helper.RED, | |
| 125 'green': helper.GREEN, | |
| 126 'yellow': helper.YELLOW, | |
| 127 'blue': helper.BLUE, | |
| 128 'magenta': helper.MAGENTA, | |
| 129 'cyan': helper.CYAN, | |
| 130 'white': helper.WHITE} | |
| 131 | |
| 132 def __getattr__(self, name): | |
| 133 try: | |
| 134 return self.ground(self.attrs[name]) | |
| 135 except KeyError: | |
| 136 raise AttributeError(name) | |
| 137 | |
| 138 fg = _ColorAttribute(_ForegroundColorAttr) | |
| 139 bg = _ColorAttribute(_BackgroundColorAttr) | |
| 140 | |
| 141 attrs = { | |
| 142 'bold': insults.BOLD, | |
| 143 'blink': insults.BLINK, | |
| 144 'underline': insults.UNDERLINE, | |
| 145 'reverseVideo': insults.REVERSE_VIDEO} | |
| 146 | |
| 147 def __getattr__(self, name): | |
| 148 if name == 'normal': | |
| 149 return _NormalAttr() | |
| 150 if name in self.attrs: | |
| 151 return _OtherAttr(name, True) | |
| 152 raise AttributeError(name) | |
| 153 | |
| 154 def flatten(output, attrs): | |
| 155 """Serialize a sequence of characters with attribute information | |
| 156 | |
| 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. | |
| 161 | |
| 162 For example, if your terminal is VT102 compatible, you might run | |
| 163 this for a colorful variation on the \"hello world\" theme:: | |
| 164 | |
| 165 | from twisted.conch.insults.text import flatten, attributes as A | |
| 166 | from twisted.conch.insults.helper import CharacterAttribute | |
| 167 | print flatten( | |
| 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()) | |
| 171 | |
| 172 @param output: Object returned by accessing attributes of the | |
| 173 module-level attributes object. | |
| 174 | |
| 175 @param attrs: A L{twisted.conch.insults.helper.CharacterAttribute} | |
| 176 instance | |
| 177 | |
| 178 @return: A VT102-friendly string | |
| 179 """ | |
| 180 L = [] | |
| 181 output.serialize(L.append, attrs) | |
| 182 return ''.join(L) | |
| 183 | |
| 184 attributes = CharacterAttributes() | |
| 185 | |
| 186 __all__ = ['attributes', 'flatten'] | |
| OLD | NEW |