123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- # vim: fileencoding=utf-8
- #
- # The Qubes OS Project, https://www.qubes-os.org/
- #
- # Copyright (C) 2017 Wojtek Porczyk <woju@invisiblethingslab.com>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU Lesser General Public License as published by
- # the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with this program; if not, see <http://www.gnu.org/licenses/>.
- #
- '''Qubes CLI spinner
- A novice asked the master: “In the east there is a great tree-structure that
- men call `Corporate Headquarters'. It is bloated out of shape with vice
- presidents and accountants. It issues a multitude of memos, each saying `Go,
- Hence!' or `Go, Hither!' and nobody knows what is meant. Every year new names
- are put onto the branches, but all to no avail. How can such an unnatural
- entity be?"
- The master replied: “You perceive this immense structure and are disturbed that
- it has no rational purpose. Can you not take amusement from its endless
- gyrations? Do you not enjoy the untroubled ease of programming beneath its
- sheltering branches? Why are you bothered by its uselessness?”
- (Geoffrey James, “The Tao of Programming”, 7.1)
- '''
- import curses
- import io
- import itertools
- CHARSET = '-\\|/'
- ENTERPRISE_CHARSET = CHARSET * 4 + '-._.-^' * 2
- class AbstractSpinner(object):
- '''The base class for all Spinners
- :param stream: file-like object with ``.write()`` method
- :param str charset: the sequence of characters to display
- The spinner should be used as follows:
- 1. exactly one call to :py:meth:`show()`
- 2. zero or more calls to :py:meth:`update()`
- 3. exactly one call to :py:meth:`hide()`
- '''
- def __init__(self, stream, charset=CHARSET):
- self.stream = stream
- self.charset = itertools.cycle(charset)
- def show(self, prompt):
- '''Show the spinner, with a prompt
- :param str prompt: prompt, like "please wait"
- '''
- raise NotImplementedError()
- def hide(self):
- '''Hide the spinner and the prompt'''
- raise NotImplementedError()
- def update(self):
- '''Show next spinner character'''
- raise NotImplementedError()
- class DummySpinner(AbstractSpinner):
- '''Dummy spinner, does not do anything'''
- def show(self, prompt):
- pass
- def hide(self):
- pass
- def update(self):
- pass
- class QubesSpinner(AbstractSpinner):
- '''Basic spinner
- This spinner uses standard ASCII control characters'''
- def __init__(self, *args, **kwargs):
- super(QubesSpinner, self).__init__(*args, **kwargs)
- self.hidelen = 0
- self.cub1 = '\b'
- def show(self, prompt):
- self.hidelen = len(prompt) + 2
- self.stream.write('{} {}'.format(prompt, next(self.charset)))
- self.stream.flush()
- def hide(self):
- self.stream.write('\r' + ' ' * self.hidelen + '\r')
- self.stream.flush()
- def update(self):
- self.stream.write(self.cub1 + next(self.charset))
- self.stream.flush()
- class QubesSpinnerEnterpriseEdition(QubesSpinner):
- '''Enterprise spinner
- This is tty- and terminfo-aware spinner. Recommended.
- '''
- def __init__(self, stream, charset=None):
- # our Enterprise logic follows
- self.stream_isatty = stream.isatty()
- if charset is None:
- charset = ENTERPRISE_CHARSET if self.stream_isatty else '.'
- super(QubesSpinnerEnterpriseEdition, self).__init__(stream, charset)
- if self.stream_isatty:
- try:
- curses.setupterm()
- self.has_terminfo = True
- self.cub1 = curses.tigetstr('cub1').decode()
- except (curses.error, io.UnsupportedOperation):
- # we are in very non-Enterprise environment
- self.has_terminfo = False
- else:
- self.cub1 = ''
- def hide(self):
- if self.stream_isatty:
- hideseq = '\r' + ' ' * self.hidelen + '\r'
- if self.has_terminfo:
- hideseq_l = (curses.tigetstr('cr'), curses.tigetstr('clr_eol'))
- if all(seq is not None for seq in hideseq_l):
- hideseq = ''.join(seq.decode() for seq in hideseq_l)
- else:
- hideseq = '\n'
- self.stream.write(hideseq)
- self.stream.flush()
|