PageRenderTime 44ms CodeModel.GetById 14ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/lib/python/indra/util/term.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 222 lines | 213 code | 0 blank | 9 comment | 0 complexity | c0221ef6c3797ff72ae38d09bd4f2f02 MD5 | raw file
  1'''
  2@file term.py
  3@brief a better shutil.copytree replacement
  4
  5$LicenseInfo:firstyear=2007&license=mit$
  6
  7Copyright (c) 2007-2009, Linden Research, Inc.
  8
  9Permission is hereby granted, free of charge, to any person obtaining a copy
 10of this software and associated documentation files (the "Software"), to deal
 11in the Software without restriction, including without limitation the rights
 12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13copies of the Software, and to permit persons to whom the Software is
 14furnished to do so, subject to the following conditions:
 15
 16The above copyright notice and this permission notice shall be included in
 17all copies or substantial portions of the Software.
 18
 19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 25THE SOFTWARE.
 26$/LicenseInfo$
 27'''
 28
 29#http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116
 30
 31import sys, re
 32
 33class 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
182class 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