Coverage for /media/ldata/code/tendril/tendril/utils/terminal.py: 65%

# Coverage for /media/ldata/code/tendril/tendril/utils/terminal.py : 65%

## 125 statements 81 run 44 missing 0 excluded

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | #!/usr/bin/env python # encoding: utf-8 # Copyright (C) 2015 Chintalagiri Shashank # # This file is part of tendril. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ The Terminal Utils Module (:mod:`tendril.utils.terminal`) ========================================================= This module provides utils for rendering basic UI elements on the terminal. :class:`TendrilProgressBar can be used to produce animated progress bars on the terminal. This class (and the code related) to it is essentially a copy of pip`s progressbar implementation in pip.utils.ui. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import sys import os import shlex import struct import platform import subprocess import six from time import time from progress.bar import Bar from progress.bar import IncrementalBar try: import colorama # Lots of different errors can come from this, including SystemError and # ImportError. except Exception: colorama = None WINDOWS = (sys.platform.startswith("win") or (sys.platform == 'cli' and os.name == 'nt')) def get_terminal_width(): return get_terminal_size()[0] def get_terminal_size(): """ getTerminalSize() - get width and height of console - works on linux,os x,windows,cygwin(windows) Taken from https://gist.github.com/jtriley/1108174 """ current_os = platform.system() tuple_xy = None if current_os == 'Windows': tuple_xy = _get_terminal_size_windows() if tuple_xy is None: tuple_xy = _get_terminal_size_tput() # needed for window's python in cygwin's xterm! if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'): tuple_xy = _get_terminal_size_linux() if tuple_xy is None: tuple_xy = (80, 25) # default value return tuple_xy def _get_terminal_size_windows(): try: from ctypes import windll, create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 return sizex, sizey except: pass def _get_terminal_size_tput(): # get terminal width try: cols = int(subprocess.check_call(shlex.split('tput cols'))) rows = int(subprocess.check_call(shlex.split('tput lines'))) return cols, rows except: pass def _get_terminal_size_linux(): def ioctl_GWINSZ(fd): try: import fcntl import termios cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) return cr except: pass cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environ['LINES'], os.environ['COLUMNS']) except: return None return int(cr[1]), int(cr[0]) def _select_progress_class(preferred, fallback): encoding = getattr(preferred.file, "encoding", None) # If we don't know what encoding this file is in, then we'll just assume # that it doesn't support unicode and use the ASCII bar. if not encoding: return fallback # Collect all of the possible characters we want to use with the preferred # bar. characters = [ getattr(preferred, "empty_fill", six.text_type()), getattr(preferred, "fill", six.text_type()), ] characters += list(getattr(preferred, "phases", [])) # Try to decode the characters we're using for the bar using the encoding # of the given file, if this works then we'll assume that we can use the # fancier bar and if not we'll fall back to the plaintext bar. try: six.text_type().join(characters).encode(encoding) except UnicodeEncodeError: return fallback else: return preferred _BaseBar = _select_progress_class(IncrementalBar, Bar) class WindowsMixin(object): def __init__(self, *args, **kwargs): # The Windows terminal does not support the hide/show cursor ANSI # codes even with colorama. So we'll ensure that hide_cursor is False # on Windows. # This call neds to go before the super() call, so that hide_cursor # is set in time. The base progress bar class writes the "hide cursor" # code to the terminal in its init, so if we don't set this soon # enough, we get a "hide" with no corresponding "show"... if WINDOWS and self.hide_cursor: self.hide_cursor = False super(WindowsMixin, self).__init__(*args, **kwargs) # Check if we are running on Windows and we have the colorama module, # if we do then wrap our file with it. if WINDOWS and colorama: self.file = colorama.AnsiToWin32(self.file) # The progress code expects to be able to call self.file.isatty() # but the colorama.AnsiToWin32() object doesn't have that, so # we'll add it. self.file.isatty = lambda: self.file.wrapped.isatty() # The progress code expects to be able to call self.file.flush() # but the colorama.AnsiToWin32() object doesn't have that, so # we'll add it. self.file.flush = lambda: self.file.wrapped.flush() class TendrilProgressBar(WindowsMixin, _BaseBar): """ This class can be used from other modules to provide a consistent feel to progress bars across tendril. It adds a ``note`` keyword argument to the ``next()`` function, and renders the note after the suffixe of the progress bar. .. rubric :: Usage >>> from tendril.utils.terminal import TendrilProgressBar >>> pb = TendrilProgressBar(max=100) >>> for i in range(100): ... pb.next(note=i) """ file = sys.stdout message = "%(percent)3d%%" suffix = "ETA %(eta_td)s" def __init__(self, *args, **kwargs): super(TendrilProgressBar, self).__init__(*args, **kwargs) self._note = None self._term_width = get_terminal_width() @property def term_width(self): return self._term_width def next(self, n=1, note=None): if n > 0: now = time() dt = (now - self._ts) / n self._dt.append(dt) self._ts = now self.index += n self._note = str(note) self.update() def writeln(self, line): if self.file.isatty(): self.clearln() if self._note is not None: line = ' '.join([line, self._note]) if len(line) > self.term_width: line = line[:self.term_width] print(line, end='', file=self.file) self.file.flush()