diff options
author | Jacek Antonelli | 2008-09-06 18:24:57 -0500 |
---|---|---|
committer | Jacek Antonelli | 2008-09-06 18:25:07 -0500 |
commit | 798d367d54a6c6379ad355bd8345fa40e31e7fe9 (patch) | |
tree | 1921f1708cd0240648c97bc02df2c2ab5f2fc41e /linden/indra/lib/python/indra/util/term.py | |
parent | Second Life viewer sources 1.20.15 (diff) | |
download | meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.zip meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.gz meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.bz2 meta-impy-798d367d54a6c6379ad355bd8345fa40e31e7fe9.tar.xz |
Second Life viewer sources 1.21.0-RC
Diffstat (limited to '')
-rw-r--r-- | linden/indra/lib/python/indra/util/term.py | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/linden/indra/lib/python/indra/util/term.py b/linden/indra/lib/python/indra/util/term.py new file mode 100644 index 0000000..8238b78 --- /dev/null +++ b/linden/indra/lib/python/indra/util/term.py | |||
@@ -0,0 +1,222 @@ | |||
1 | ''' | ||
2 | @file term.py | ||
3 | @brief a better shutil.copytree replacement | ||
4 | |||
5 | $LicenseInfo:firstyear=2007&license=mit$ | ||
6 | |||
7 | Copyright (c) 2007-2008, Linden Research, Inc. | ||
8 | |||
9 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
10 | of this software and associated documentation files (the "Software"), to deal | ||
11 | in the Software without restriction, including without limitation the rights | ||
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
13 | copies of the Software, and to permit persons to whom the Software is | ||
14 | furnished to do so, subject to the following conditions: | ||
15 | |||
16 | The above copyright notice and this permission notice shall be included in | ||
17 | all copies or substantial portions of the Software. | ||
18 | |||
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
25 | THE SOFTWARE. | ||
26 | $/LicenseInfo$ | ||
27 | ''' | ||
28 | |||
29 | #http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116 | ||
30 | |||
31 | import sys, re | ||
32 | |||
33 | class TerminalController: | ||
34 | """ | ||
35 | A class that can be used to portably generate formatted output to | ||
36 | a terminal. | ||
37 | |||
38 | `TerminalController` defines a set of instance variables whose | ||
39 | values are initialized to the control sequence necessary to | ||
40 | perform a given action. These can be simply included in normal | ||
41 | output to the terminal: | ||
42 | |||
43 | >>> term = TerminalController() | ||
44 | >>> print 'This is '+term.GREEN+'green'+term.NORMAL | ||
45 | |||
46 | Alternatively, the `render()` method can used, which replaces | ||
47 | '${action}' with the string required to perform 'action': | ||
48 | |||
49 | >>> term = TerminalController() | ||
50 | >>> print term.render('This is ${GREEN}green${NORMAL}') | ||
51 | |||
52 | If the terminal doesn't support a given action, then the value of | ||
53 | the corresponding instance variable will be set to ''. As a | ||
54 | result, the above code will still work on terminals that do not | ||
55 | support color, except that their output will not be colored. | ||
56 | Also, this means that you can test whether the terminal supports a | ||
57 | given action by simply testing the truth value of the | ||
58 | corresponding instance variable: | ||
59 | |||
60 | >>> term = TerminalController() | ||
61 | >>> if term.CLEAR_SCREEN: | ||
62 | ... print 'This terminal supports clearning the screen.' | ||
63 | |||
64 | Finally, if the width and height of the terminal are known, then | ||
65 | they will be stored in the `COLS` and `LINES` attributes. | ||
66 | """ | ||
67 | # Cursor movement: | ||
68 | BOL = '' #: Move the cursor to the beginning of the line | ||
69 | UP = '' #: Move the cursor up one line | ||
70 | DOWN = '' #: Move the cursor down one line | ||
71 | LEFT = '' #: Move the cursor left one char | ||
72 | RIGHT = '' #: Move the cursor right one char | ||
73 | |||
74 | # Deletion: | ||
75 | CLEAR_SCREEN = '' #: Clear the screen and move to home position | ||
76 | CLEAR_EOL = '' #: Clear to the end of the line. | ||
77 | CLEAR_BOL = '' #: Clear to the beginning of the line. | ||
78 | CLEAR_EOS = '' #: Clear to the end of the screen | ||
79 | |||
80 | # Output modes: | ||
81 | BOLD = '' #: Turn on bold mode | ||
82 | BLINK = '' #: Turn on blink mode | ||
83 | DIM = '' #: Turn on half-bright mode | ||
84 | REVERSE = '' #: Turn on reverse-video mode | ||
85 | NORMAL = '' #: Turn off all modes | ||
86 | |||
87 | # Cursor display: | ||
88 | HIDE_CURSOR = '' #: Make the cursor invisible | ||
89 | SHOW_CURSOR = '' #: Make the cursor visible | ||
90 | |||
91 | # Terminal size: | ||
92 | COLS = None #: Width of the terminal (None for unknown) | ||
93 | LINES = None #: Height of the terminal (None for unknown) | ||
94 | |||
95 | # Foreground colors: | ||
96 | BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' | ||
97 | |||
98 | # Background colors: | ||
99 | BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' | ||
100 | BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' | ||
101 | |||
102 | _STRING_CAPABILITIES = """ | ||
103 | BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 | ||
104 | CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold | ||
105 | BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 | ||
106 | HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() | ||
107 | _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() | ||
108 | _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() | ||
109 | |||
110 | def __init__(self, term_stream=sys.stdout): | ||
111 | """ | ||
112 | Create a `TerminalController` and initialize its attributes | ||
113 | with appropriate values for the current terminal. | ||
114 | `term_stream` is the stream that will be used for terminal | ||
115 | output; if this stream is not a tty, then the terminal is | ||
116 | assumed to be a dumb terminal (i.e., have no capabilities). | ||
117 | """ | ||
118 | # Curses isn't available on all platforms | ||
119 | try: import curses | ||
120 | except: return | ||
121 | |||
122 | # If the stream isn't a tty, then assume it has no capabilities. | ||
123 | if not term_stream.isatty(): return | ||
124 | |||
125 | # Check the terminal type. If we fail, then assume that the | ||
126 | # terminal has no capabilities. | ||
127 | try: curses.setupterm() | ||
128 | except: return | ||
129 | |||
130 | # Look up numeric capabilities. | ||
131 | self.COLS = curses.tigetnum('cols') | ||
132 | self.LINES = curses.tigetnum('lines') | ||
133 | |||
134 | # Look up string capabilities. | ||
135 | for capability in self._STRING_CAPABILITIES: | ||
136 | (attrib, cap_name) = capability.split('=') | ||
137 | setattr(self, attrib, self._tigetstr(cap_name) or '') | ||
138 | |||
139 | # Colors | ||
140 | set_fg = self._tigetstr('setf') | ||
141 | if set_fg: | ||
142 | for i,color in zip(range(len(self._COLORS)), self._COLORS): | ||
143 | setattr(self, color, curses.tparm(set_fg, i) or '') | ||
144 | set_fg_ansi = self._tigetstr('setaf') | ||
145 | if set_fg_ansi: | ||
146 | for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): | ||
147 | setattr(self, color, curses.tparm(set_fg_ansi, i) or '') | ||
148 | set_bg = self._tigetstr('setb') | ||
149 | if set_bg: | ||
150 | for i,color in zip(range(len(self._COLORS)), self._COLORS): | ||
151 | setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '') | ||
152 | set_bg_ansi = self._tigetstr('setab') | ||
153 | if set_bg_ansi: | ||
154 | for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): | ||
155 | setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') | ||
156 | |||
157 | def _tigetstr(self, cap_name): | ||
158 | # String capabilities can include "delays" of the form "$<2>". | ||
159 | # For any modern terminal, we should be able to just ignore | ||
160 | # these, so strip them out. | ||
161 | import curses | ||
162 | cap = curses.tigetstr(cap_name) or '' | ||
163 | return re.sub(r'\$<\d+>[/*]?', '', cap) | ||
164 | |||
165 | def render(self, template): | ||
166 | """ | ||
167 | Replace each $-substitutions in the given template string with | ||
168 | the corresponding terminal control string (if it's defined) or | ||
169 | '' (if it's not). | ||
170 | """ | ||
171 | return re.sub(r'\$\$|\${\w+}', self._render_sub, template) | ||
172 | |||
173 | def _render_sub(self, match): | ||
174 | s = match.group() | ||
175 | if s == '$$': return s | ||
176 | else: return getattr(self, s[2:-1]) | ||
177 | |||
178 | ####################################################################### | ||
179 | # Example use case: progress bar | ||
180 | ####################################################################### | ||
181 | |||
182 | class ProgressBar: | ||
183 | """ | ||
184 | A 3-line progress bar, which looks like:: | ||
185 | |||
186 | Header | ||
187 | 20% [===========----------------------------------] | ||
188 | progress message | ||
189 | |||
190 | The progress bar is colored, if the terminal supports color | ||
191 | output; and adjusts to the width of the terminal. | ||
192 | """ | ||
193 | BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n' | ||
194 | HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' | ||
195 | |||
196 | def __init__(self, term, header): | ||
197 | self.term = term | ||
198 | if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): | ||
199 | raise ValueError("Terminal isn't capable enough -- you " | ||
200 | "should use a simpler progress dispaly.") | ||
201 | self.width = self.term.COLS or 75 | ||
202 | self.bar = term.render(self.BAR) | ||
203 | self.header = self.term.render(self.HEADER % header.center(self.width)) | ||
204 | self.cleared = 1 #: true if we haven't drawn the bar yet. | ||
205 | self.update(0, '') | ||
206 | |||
207 | def update(self, percent, message): | ||
208 | if self.cleared: | ||
209 | sys.stdout.write(self.header) | ||
210 | self.cleared = 0 | ||
211 | n = int((self.width-10)*percent) | ||
212 | sys.stdout.write( | ||
213 | self.term.BOL + self.term.UP + self.term.CLEAR_EOL + | ||
214 | (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) + | ||
215 | self.term.CLEAR_EOL + message.center(self.width)) | ||
216 | |||
217 | def clear(self): | ||
218 | if not self.cleared: | ||
219 | sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + | ||
220 | self.term.UP + self.term.CLEAR_EOL + | ||
221 | self.term.UP + self.term.CLEAR_EOL) | ||
222 | self.cleared = 1 | ||