diff options
Diffstat (limited to 'utils/lit/lit/ProgressBar.py')
| -rw-r--r-- | utils/lit/lit/ProgressBar.py | 267 | 
1 files changed, 267 insertions, 0 deletions
diff --git a/utils/lit/lit/ProgressBar.py b/utils/lit/lit/ProgressBar.py new file mode 100644 index 000000000000..85c95f57f7ac --- /dev/null +++ b/utils/lit/lit/ProgressBar.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +# Source: http://code.activestate.com/recipes/475116/, with +# modifications by Daniel Dunbar. + +import sys, re, time + +class TerminalController: +    """ +    A class that can be used to portably generate formatted output to +    a terminal.   +     +    `TerminalController` defines a set of instance variables whose +    values are initialized to the control sequence necessary to +    perform a given action.  These can be simply included in normal +    output to the terminal: + +        >>> term = TerminalController() +        >>> print 'This is '+term.GREEN+'green'+term.NORMAL + +    Alternatively, the `render()` method can used, which replaces +    '${action}' with the string required to perform 'action': + +        >>> term = TerminalController() +        >>> print term.render('This is ${GREEN}green${NORMAL}') + +    If the terminal doesn't support a given action, then the value of +    the corresponding instance variable will be set to ''.  As a +    result, the above code will still work on terminals that do not +    support color, except that their output will not be colored. +    Also, this means that you can test whether the terminal supports a +    given action by simply testing the truth value of the +    corresponding instance variable: + +        >>> term = TerminalController() +        >>> if term.CLEAR_SCREEN: +        ...     print 'This terminal supports clearning the screen.' + +    Finally, if the width and height of the terminal are known, then +    they will be stored in the `COLS` and `LINES` attributes. +    """ +    # Cursor movement: +    BOL = ''             #: Move the cursor to the beginning of the line +    UP = ''              #: Move the cursor up one line +    DOWN = ''            #: Move the cursor down one line +    LEFT = ''            #: Move the cursor left one char +    RIGHT = ''           #: Move the cursor right one char + +    # Deletion: +    CLEAR_SCREEN = ''    #: Clear the screen and move to home position +    CLEAR_EOL = ''       #: Clear to the end of the line. +    CLEAR_BOL = ''       #: Clear to the beginning of the line. +    CLEAR_EOS = ''       #: Clear to the end of the screen + +    # Output modes: +    BOLD = ''            #: Turn on bold mode +    BLINK = ''           #: Turn on blink mode +    DIM = ''             #: Turn on half-bright mode +    REVERSE = ''         #: Turn on reverse-video mode +    NORMAL = ''          #: Turn off all modes + +    # Cursor display: +    HIDE_CURSOR = ''     #: Make the cursor invisible +    SHOW_CURSOR = ''     #: Make the cursor visible + +    # Terminal size: +    COLS = None          #: Width of the terminal (None for unknown) +    LINES = None         #: Height of the terminal (None for unknown) + +    # Foreground colors: +    BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' +     +    # Background colors: +    BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' +    BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' +     +    _STRING_CAPABILITIES = """ +    BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 +    CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold +    BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 +    HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() +    _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() +    _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() + +    def __init__(self, term_stream=sys.stdout): +        """ +        Create a `TerminalController` and initialize its attributes +        with appropriate values for the current terminal. +        `term_stream` is the stream that will be used for terminal +        output; if this stream is not a tty, then the terminal is +        assumed to be a dumb terminal (i.e., have no capabilities). +        """ +        # Curses isn't available on all platforms +        try: import curses +        except: return + +        # If the stream isn't a tty, then assume it has no capabilities. +        if not term_stream.isatty(): return + +        # Check the terminal type.  If we fail, then assume that the +        # terminal has no capabilities. +        try: curses.setupterm() +        except: return + +        # Look up numeric capabilities. +        self.COLS = curses.tigetnum('cols') +        self.LINES = curses.tigetnum('lines') +         +        # Look up string capabilities. +        for capability in self._STRING_CAPABILITIES: +            (attrib, cap_name) = capability.split('=') +            setattr(self, attrib, self._tigetstr(cap_name) or '') + +        # Colors +        set_fg = self._tigetstr('setf') +        if set_fg: +            for i,color in zip(range(len(self._COLORS)), self._COLORS): +                setattr(self, color, curses.tparm(set_fg, i) or '') +        set_fg_ansi = self._tigetstr('setaf') +        if set_fg_ansi: +            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): +                setattr(self, color, curses.tparm(set_fg_ansi, i) or '') +        set_bg = self._tigetstr('setb') +        if set_bg: +            for i,color in zip(range(len(self._COLORS)), self._COLORS): +                setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '') +        set_bg_ansi = self._tigetstr('setab') +        if set_bg_ansi: +            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): +                setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') + +    def _tigetstr(self, cap_name): +        # String capabilities can include "delays" of the form "$<2>". +        # For any modern terminal, we should be able to just ignore +        # these, so strip them out. +        import curses +        cap = curses.tigetstr(cap_name) or '' +        return re.sub(r'\$<\d+>[/*]?', '', cap) + +    def render(self, template): +        """ +        Replace each $-substitutions in the given template string with +        the corresponding terminal control string (if it's defined) or +        '' (if it's not). +        """ +        return re.sub(r'\$\$|\${\w+}', self._render_sub, template) + +    def _render_sub(self, match): +        s = match.group() +        if s == '$$': return s +        else: return getattr(self, s[2:-1]) + +####################################################################### +# Example use case: progress bar +####################################################################### + +class SimpleProgressBar: +    """ +    A simple progress bar which doesn't need any terminal support. + +    This prints out a progress bar like: +      'Header: 0 .. 10.. 20.. ...' +    """ + +    def __init__(self, header): +        self.header = header +        self.atIndex = None + +    def update(self, percent, message): +        if self.atIndex is None: +            sys.stdout.write(self.header) +            self.atIndex = 0 + +        next = int(percent*50) +        if next == self.atIndex: +            return + +        for i in range(self.atIndex, next): +            idx = i % 5 +            if idx == 0: +                sys.stdout.write('%-2d' % (i*2)) +            elif idx == 1: +                pass # Skip second char +            elif idx < 4: +                sys.stdout.write('.') +            else: +                sys.stdout.write(' ') +        sys.stdout.flush() +        self.atIndex = next + +    def clear(self): +        if self.atIndex is not None: +            sys.stdout.write('\n') +            sys.stdout.flush() +            self.atIndex = None + +class ProgressBar: +    """ +    A 3-line progress bar, which looks like:: +     +                                Header +        20% [===========----------------------------------] +                           progress message + +    The progress bar is colored, if the terminal supports color +    output; and adjusts to the width of the terminal. +    """ +    BAR = '%s${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}%s\n' +    HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' +         +    def __init__(self, term, header, useETA=True): +        self.term = term +        if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): +            raise ValueError("Terminal isn't capable enough -- you " +                             "should use a simpler progress dispaly.") +        self.width = self.term.COLS or 75 +        self.bar = term.render(self.BAR) +        self.header = self.term.render(self.HEADER % header.center(self.width)) +        self.cleared = 1 #: true if we haven't drawn the bar yet. +        self.useETA = useETA +        if self.useETA: +            self.startTime = time.time() +        self.update(0, '') + +    def update(self, percent, message): +        if self.cleared: +            sys.stdout.write(self.header) +            self.cleared = 0 +        prefix = '%3d%% ' % (percent*100,) +        suffix = '' +        if self.useETA: +            elapsed = time.time() - self.startTime +            if percent > .0001 and elapsed > 1: +                total = elapsed / percent +                eta = int(total - elapsed) +                h = eta//3600. +                m = (eta//60) % 60 +                s = eta % 60 +                suffix = ' ETA: %02d:%02d:%02d'%(h,m,s) +        barWidth = self.width - len(prefix) - len(suffix) - 2 +        n = int(barWidth*percent) +        if len(message) < self.width: +            message = message + ' '*(self.width - len(message)) +        else: +            message = '... ' + message[-(self.width-4):] +        sys.stdout.write( +            self.term.BOL + self.term.UP + self.term.CLEAR_EOL + +            (self.bar % (prefix, '='*n, '-'*(barWidth-n), suffix)) + +            self.term.CLEAR_EOL + message) + +    def clear(self): +        if not self.cleared: +            sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + +                             self.term.UP + self.term.CLEAR_EOL + +                             self.term.UP + self.term.CLEAR_EOL) +            self.cleared = 1 + +def test(): +    import time +    tc = TerminalController() +    p = ProgressBar(tc, 'Tests') +    for i in range(101): +        p.update(i/100., str(i))         +        time.sleep(.3) + +if __name__=='__main__': +    test()  | 
