1be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar#!/usr/bin/env python 2be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 3be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar# Source: http://code.activestate.com/recipes/475116/, with 4be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar# modifications by Daniel Dunbar. 5be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 6be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbarimport sys, re, time 7be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 84db169d31e34df89d10660960070de073f0f28afDaniel Dunbardef to_bytes(str): 94db169d31e34df89d10660960070de073f0f28afDaniel Dunbar # Encode to Latin1 to get binary data. 104db169d31e34df89d10660960070de073f0f28afDaniel Dunbar return str.encode('ISO-8859-1') 114db169d31e34df89d10660960070de073f0f28afDaniel Dunbar 12be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbarclass TerminalController: 13be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 14be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar A class that can be used to portably generate formatted output to 15be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar a terminal. 16be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 17be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar `TerminalController` defines a set of instance variables whose 18be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar values are initialized to the control sequence necessary to 19be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar perform a given action. These can be simply included in normal 20be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar output to the terminal: 21be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 22be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar >>> term = TerminalController() 230d038e3e8852bf4fde949136ca9c2815f64febd0Daniel Dunbar >>> print('This is '+term.GREEN+'green'+term.NORMAL) 24be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 25be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Alternatively, the `render()` method can used, which replaces 26be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar '${action}' with the string required to perform 'action': 27be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 28be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar >>> term = TerminalController() 290d038e3e8852bf4fde949136ca9c2815f64febd0Daniel Dunbar >>> print(term.render('This is ${GREEN}green${NORMAL}')) 30be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 31be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar If the terminal doesn't support a given action, then the value of 32be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar the corresponding instance variable will be set to ''. As a 33be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar result, the above code will still work on terminals that do not 34be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar support color, except that their output will not be colored. 35be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Also, this means that you can test whether the terminal supports a 36be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar given action by simply testing the truth value of the 37be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar corresponding instance variable: 38be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 39be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar >>> term = TerminalController() 40be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar >>> if term.CLEAR_SCREEN: 410d038e3e8852bf4fde949136ca9c2815f64febd0Daniel Dunbar ... print('This terminal supports clearning the screen.') 42be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 43be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Finally, if the width and height of the terminal are known, then 44be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar they will be stored in the `COLS` and `LINES` attributes. 45be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 46be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Cursor movement: 47be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BOL = '' #: Move the cursor to the beginning of the line 48be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar UP = '' #: Move the cursor up one line 49be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar DOWN = '' #: Move the cursor down one line 50be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar LEFT = '' #: Move the cursor left one char 51be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar RIGHT = '' #: Move the cursor right one char 52be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 53be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Deletion: 54be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar CLEAR_SCREEN = '' #: Clear the screen and move to home position 55be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar CLEAR_EOL = '' #: Clear to the end of the line. 56be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar CLEAR_BOL = '' #: Clear to the beginning of the line. 57be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar CLEAR_EOS = '' #: Clear to the end of the screen 58be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 59be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Output modes: 60be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BOLD = '' #: Turn on bold mode 61be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BLINK = '' #: Turn on blink mode 62be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar DIM = '' #: Turn on half-bright mode 63be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar REVERSE = '' #: Turn on reverse-video mode 64be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar NORMAL = '' #: Turn off all modes 65be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 66be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Cursor display: 67be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar HIDE_CURSOR = '' #: Make the cursor invisible 68be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar SHOW_CURSOR = '' #: Make the cursor visible 69be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 70be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Terminal size: 71be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar COLS = None #: Width of the terminal (None for unknown) 72be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar LINES = None #: Height of the terminal (None for unknown) 73be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 74be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Foreground colors: 75be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' 76be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 77be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Background colors: 78be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' 79be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' 80be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 81be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar _STRING_CAPABILITIES = """ 82be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 83be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold 84be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 85be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() 86be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() 87be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() 88be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 89be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def __init__(self, term_stream=sys.stdout): 90be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 91be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Create a `TerminalController` and initialize its attributes 92be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar with appropriate values for the current terminal. 93be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar `term_stream` is the stream that will be used for terminal 94be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar output; if this stream is not a tty, then the terminal is 95be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar assumed to be a dumb terminal (i.e., have no capabilities). 96be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 97be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Curses isn't available on all platforms 98be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar try: import curses 99be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar except: return 100be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 101be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # If the stream isn't a tty, then assume it has no capabilities. 102be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if not term_stream.isatty(): return 103be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 104be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Check the terminal type. If we fail, then assume that the 105be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # terminal has no capabilities. 106be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar try: curses.setupterm() 107be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar except: return 108be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 109be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Look up numeric capabilities. 110be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.COLS = curses.tigetnum('cols') 111be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.LINES = curses.tigetnum('lines') 1128ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.XN = curses.tigetflag('xenl') 113be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 114be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Look up string capabilities. 115be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for capability in self._STRING_CAPABILITIES: 116be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar (attrib, cap_name) = capability.split('=') 117be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar setattr(self, attrib, self._tigetstr(cap_name) or '') 118be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 119be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # Colors 120be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar set_fg = self._tigetstr('setf') 121be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if set_fg: 122be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i,color in zip(range(len(self._COLORS)), self._COLORS): 1234db169d31e34df89d10660960070de073f0f28afDaniel Dunbar setattr(self, color, self._tparm(set_fg, i)) 124be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar set_fg_ansi = self._tigetstr('setaf') 125be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if set_fg_ansi: 126be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): 1274db169d31e34df89d10660960070de073f0f28afDaniel Dunbar setattr(self, color, self._tparm(set_fg_ansi, i)) 128be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar set_bg = self._tigetstr('setb') 129be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if set_bg: 130be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i,color in zip(range(len(self._COLORS)), self._COLORS): 1314db169d31e34df89d10660960070de073f0f28afDaniel Dunbar setattr(self, 'BG_'+color, self._tparm(set_bg, i)) 132be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar set_bg_ansi = self._tigetstr('setab') 133be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if set_bg_ansi: 134be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): 1354db169d31e34df89d10660960070de073f0f28afDaniel Dunbar setattr(self, 'BG_'+color, self._tparm(set_bg_ansi, i)) 1364db169d31e34df89d10660960070de073f0f28afDaniel Dunbar 1374db169d31e34df89d10660960070de073f0f28afDaniel Dunbar def _tparm(self, arg, index): 1384db169d31e34df89d10660960070de073f0f28afDaniel Dunbar import curses 1394db169d31e34df89d10660960070de073f0f28afDaniel Dunbar return curses.tparm(to_bytes(arg), index).decode('ascii') or '' 140be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 141be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def _tigetstr(self, cap_name): 142be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # String capabilities can include "delays" of the form "$<2>". 143be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # For any modern terminal, we should be able to just ignore 144be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar # these, so strip them out. 145be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar import curses 14605fb743a99ac6a1fc147682a2262a6190f193ab4Daniel Dunbar cap = curses.tigetstr(cap_name) 14705fb743a99ac6a1fc147682a2262a6190f193ab4Daniel Dunbar if cap is None: 14805fb743a99ac6a1fc147682a2262a6190f193ab4Daniel Dunbar cap = '' 14905fb743a99ac6a1fc147682a2262a6190f193ab4Daniel Dunbar else: 15005fb743a99ac6a1fc147682a2262a6190f193ab4Daniel Dunbar cap = cap.decode('ascii') 151be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar return re.sub(r'\$<\d+>[/*]?', '', cap) 152be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 153be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def render(self, template): 154be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 155be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Replace each $-substitutions in the given template string with 156be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar the corresponding terminal control string (if it's defined) or 157be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar '' (if it's not). 158be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 159be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar return re.sub(r'\$\$|\${\w+}', self._render_sub, template) 160be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 161be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def _render_sub(self, match): 162be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar s = match.group() 163be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if s == '$$': return s 164be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar else: return getattr(self, s[2:-1]) 165be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 166be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar####################################################################### 167be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar# Example use case: progress bar 168be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar####################################################################### 169be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 170be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbarclass SimpleProgressBar: 171be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 172be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar A simple progress bar which doesn't need any terminal support. 173be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 174be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar This prints out a progress bar like: 175be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 'Header: 0 .. 10.. 20.. ...' 176be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 177be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 178be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def __init__(self, header): 179be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.header = header 180be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.atIndex = None 181be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 182be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def update(self, percent, message): 183be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if self.atIndex is None: 184be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write(self.header) 185be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.atIndex = 0 186be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 187be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar next = int(percent*50) 188be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if next == self.atIndex: 189be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar return 190be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 191be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i in range(self.atIndex, next): 192be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar idx = i % 5 193be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if idx == 0: 194be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write('%-2d' % (i*2)) 195be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar elif idx == 1: 196be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar pass # Skip second char 197be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar elif idx < 4: 198be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write('.') 199be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar else: 200be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write(' ') 201be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.flush() 202be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.atIndex = next 203be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 204be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def clear(self): 205be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if self.atIndex is not None: 206be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write('\n') 207be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.flush() 208be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.atIndex = None 209be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 210be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbarclass ProgressBar: 211be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 212be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar A 3-line progress bar, which looks like:: 213be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 214be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar Header 215be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 20% [===========----------------------------------] 216be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar progress message 217be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 218be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar The progress bar is colored, if the terminal supports color 219be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar output; and adjusts to the width of the terminal. 220be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar """ 2218ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi BAR = '%s${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}%s' 222be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' 223be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 224be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def __init__(self, term, header, useETA=True): 225be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.term = term 226be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): 227be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar raise ValueError("Terminal isn't capable enough -- you " 228be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar "should use a simpler progress dispaly.") 2298ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.BOL = self.term.BOL # BoL from col#79 2308ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.XNL = "\n" # Newline from col#79 2318ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi if self.term.COLS: 2328ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.width = self.term.COLS 2338ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi if not self.term.XN: 2348ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.BOL = self.term.UP + self.term.BOL 2358ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.XNL = "" # Cursor must be fed to the next line 2368ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi else: 2378ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.width = 75 238be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.bar = term.render(self.BAR) 239be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.header = self.term.render(self.HEADER % header.center(self.width)) 240be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.cleared = 1 #: true if we haven't drawn the bar yet. 241be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.useETA = useETA 242be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if self.useETA: 243be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.startTime = time.time() 244be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.update(0, '') 245be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 246be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def update(self, percent, message): 247be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if self.cleared: 248be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write(self.header) 249be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.cleared = 0 250be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar prefix = '%3d%% ' % (percent*100,) 251be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar suffix = '' 252be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if self.useETA: 253be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar elapsed = time.time() - self.startTime 254be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if percent > .0001 and elapsed > 1: 255be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar total = elapsed / percent 256be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar eta = int(total - elapsed) 257be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar h = eta//3600. 258be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar m = (eta//60) % 60 259be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar s = eta % 60 260be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar suffix = ' ETA: %02d:%02d:%02d'%(h,m,s) 261be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar barWidth = self.width - len(prefix) - len(suffix) - 2 262be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar n = int(barWidth*percent) 263be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if len(message) < self.width: 264be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar message = message + ' '*(self.width - len(message)) 265be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar else: 266be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar message = '... ' + message[-(self.width-4):] 267be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar sys.stdout.write( 2688ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.BOL + self.term.UP + self.term.CLEAR_EOL + 269be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar (self.bar % (prefix, '='*n, '-'*(barWidth-n), suffix)) + 2708ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi self.XNL + 271be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.term.CLEAR_EOL + message) 2728ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi if not self.term.XN: 2738ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi sys.stdout.flush() 274be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 275be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar def clear(self): 276be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar if not self.cleared: 2778ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi sys.stdout.write(self.BOL + self.term.CLEAR_EOL + 278be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.term.UP + self.term.CLEAR_EOL + 279be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.term.UP + self.term.CLEAR_EOL) 2808ea2649fda07b945520f686c6ac0e35a15706ad1NAKAMURA Takumi sys.stdout.flush() 281be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar self.cleared = 1 282be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 283be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbardef test(): 284be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar tc = TerminalController() 285be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar p = ProgressBar(tc, 'Tests') 286be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar for i in range(101): 287be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar p.update(i/100., str(i)) 288be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar time.sleep(.3) 289be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar 290be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbarif __name__=='__main__': 291be7ada718139b8c840a38ba34c4af492b6a05f9fDaniel Dunbar test() 292