diff options
-rw-r--r-- | example2.py | 59 | ||||
-rw-r--r-- | ttystatus/messager.py | 83 | ||||
-rw-r--r-- | ttystatus/status.py | 30 |
3 files changed, 139 insertions, 33 deletions
diff --git a/example2.py b/example2.py new file mode 100644 index 0000000..4600bf2 --- /dev/null +++ b/example2.py @@ -0,0 +1,59 @@ +# Copyright 2015 Lars Wirzenius +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +'''An example program for ttystatus: compute checksums.''' + + +import hashlib +import os +import sys + +import ttystatus + + +num_bytes = 1024**2 + + +def main(): + ts = ttystatus.TerminalStatus(period=0.1) + + ts.format( + 'Elapsed time: %ElapsedTime()\n' + 'Current file: %Pathname(filename)\n' + 'File count: %Counter(filename)\n' + 'Combined size: %ByteSize(bytes-read)\n' + 'Checksum speed: %ByteSpeed(bytes-read)' + ) + ts['bytes-read'] = 0 + + for dirname, subdirs, basenames in os.walk(sys.argv[1]): + for basename in basenames: + filename = os.path.join(dirname, basename) + if os.path.isfile(filename): + ts['filename'] = filename + checksum = hashlib.sha512() + with open(filename, 'rb') as f: + data = f.read(num_bytes) + checksum.update(data) + ts['bytes-read'] += len(data) + if checksum.hexdigest().startswith('0'): + ts.notify('%s %s' % (checksum.hexdigest(), filename)) + + ts.finish() + + +if __name__ == '__main__': + main() diff --git a/ttystatus/messager.py b/ttystatus/messager.py index cfab8ad..71013e3 100644 --- a/ttystatus/messager.py +++ b/ttystatus/messager.py @@ -14,6 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +import curses import fcntl import struct import termios @@ -35,14 +36,16 @@ class Messager(object): except IOError: self.output = None self._period = 1.0 if period is None else period - self._last_msg = '' # What did we write last? self._last_time = 0 # When did we write last? self._cached_msg = '' # Last message from user, to write() method. + self._first_output = True # is our next output the first one? self._fake_width = fake_width self.set_width(self._get_terminal_width()) # Width of terminal def _open_tty(self): # pragma: no cover - return open('/dev/tty', 'w') + f = open('/dev/tty', 'wb') + curses.setupterm(None, f.fileno()) + return f def set_width(self, actual_width): self.width = actual_width - 1 @@ -78,13 +81,7 @@ class Messager(object): return width def update_width(self): # pragma: no cover - new_width = self._get_terminal_width() - if new_width != self.width: - # Clear the terminal from old stuff, using the old width. - self.clear() - # Get new width. - self.set_width(new_width) - self._overwrite(self._last_msg) + self.set_width(self._get_terminal_width()) def _raw_write(self, string): '''Write raw data if output is terminal.''' @@ -96,13 +93,6 @@ class Messager(object): except IOError: # pragma: no cover self._enabled = False - def _overwrite(self, string): - '''Overwrite current message on terminal.''' - if self._last_msg: - self._raw_write('\r' + (' ' * len(self._last_msg)) + '\r') - self._raw_write(string) - self._last_msg = string - def time_to_write(self): '''Is it time to write now?''' return self._now() - self._last_time >= self._period @@ -110,14 +100,59 @@ class Messager(object): def write(self, string): '''Write raw data, always.''' self.update_width() - string = string[:self.width] - self._overwrite(string) - self._last_time = self._now() + rows = string.split('\n') + + raw_parts = [] + + if self._first_output: + raw_parts.append('\n' * (len(rows) - 1)) + self._first_output = False + + if rows: + raw_parts.extend([ + curses.tparm(curses.tigetstr('cuu'), len(rows) - 1), # go up + curses.tigetstr('cr'), # beginning of line + curses.tigetstr('el'), # erase to end of line + rows[0][:self.width], + ]) + for row in rows[1:]: + raw_parts.extend([ + curses.tparm(curses.tigetstr('cud'), 1), # down one line + curses.tigetstr('cr'), # beginning of line + curses.tigetstr('el'), # erase to end of line + row[:self.width], + ]) + + raw = ''.join(raw_parts) + self._raw_write(raw) self._cached_msg = string + self._last_time = self._now() def clear(self): '''Remove current message from terminal.''' - self._overwrite('') + + rows = self._cached_msg.split('\n') + + raw_parts = [] + + if rows: + raw_parts.extend([ + curses.tparm(curses.tigetstr('cuu'), len(rows) - 1), # go up + curses.tigetstr('cr'), # beginning of line + curses.tigetstr('el'), # erase to end of line + ]) + for row in rows[1:]: + raw_parts.extend([ + curses.tparm(curses.tigetstr('cud'), 1), # down one line + curses.tigetstr('cr'), # beginning of line + curses.tigetstr('el'), # erase to end of line + ]) + raw_parts.extend([ + curses.tparm(curses.tigetstr('cuu'), len(rows) - 1), # go up + ]) + + raw = ''.join(raw_parts) + self._raw_write(raw) def notify(self, string, f, force=False): '''Show a notification message string to the user. @@ -133,7 +168,6 @@ class Messager(object): ''' if self._enabled or force: - old = self._last_msg self.clear() try: f.write('%s\n' % string) @@ -141,12 +175,13 @@ class Messager(object): except IOError: # We ignore these. No point in crashing if terminal is bad. pass - self._overwrite(old) + self._first_output = True + self.write(self._cached_msg) def finish(self): '''Finalize output.''' - if self._last_msg or self._cached_msg: - self._overwrite(self._cached_msg) + if self._cached_msg: + self.write(self._cached_msg) self._raw_write('\n') def disable(self): diff --git a/ttystatus/status.py b/ttystatus/status.py index 11676cb..61adcbe 100644 --- a/ttystatus/status.py +++ b/ttystatus/status.py @@ -38,7 +38,11 @@ class TerminalStatus(object): def add(self, widget): '''Add a new widget to the status display.''' - self._widgets.append(widget) + self._widget_rows[-1].append(widget) + + def start_new_line(self): + '''Start a new line of widgets.''' + self._widget_rows.append([]) def format(self, format_string): '''Add new widgets based on format string. @@ -49,12 +53,16 @@ class TerminalStatus(object): ``format("hello, %String(name)")``. ''' - for widget in ttystatus.parse(format_string): - self.add(widget) + + for i, line in enumerate(format_string.split('\n')): + if i > 0: + self.start_new_line() + for widget in ttystatus.parse(line): + self.add(widget) def clear(self): '''Remove all widgets.''' - self._widgets = [] + self._widget_rows = [[]] self._values = dict() self._m.clear() @@ -69,8 +77,9 @@ class TerminalStatus(object): def __setitem__(self, key, value): '''Set value for key.''' self._values[key] = value - for w in self._widgets: - w.update(self) + for row in self._widget_rows: + for w in row: + w.update(self) if self._m.time_to_write(): self._write() @@ -86,16 +95,19 @@ class TerminalStatus(object): def _render(self): '''Render current state of all widgets.''' + return '\n'.join(self._render_row(row) for row in self._widget_rows) + + def _render_row(self, widget_row): remaining = self._m.width - texts = [None] * len(self._widgets) + texts = [None] * len(widget_row) - for i, w in enumerate(self._widgets): + for i, w in enumerate(widget_row): if w.static_width: texts[i] = w.render(0) remaining -= len(texts[i]) - for i, w in enumerate(self._widgets): + for i, w in enumerate(widget_row): if not w.static_width: texts[i] = w.render(remaining) remaining -= len(texts[i]) |