spinner.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # vim: fileencoding=utf-8
  2. #
  3. # The Qubes OS Project, https://www.qubes-os.org/
  4. #
  5. # Copyright (C) 2017 Wojtek Porczyk <woju@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along
  18. # with this program; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. #
  21. '''Qubes CLI spinner
  22. A novice asked the master: “In the east there is a great tree-structure that
  23. men call `Corporate Headquarters'. It is bloated out of shape with vice
  24. presidents and accountants. It issues a multitude of memos, each saying `Go,
  25. Hence!' or `Go, Hither!' and nobody knows what is meant. Every year new names
  26. are put onto the branches, but all to no avail. How can such an unnatural
  27. entity be?"
  28. The master replied: “You perceive this immense structure and are disturbed that
  29. it has no rational purpose. Can you not take amusement from its endless
  30. gyrations? Do you not enjoy the untroubled ease of programming beneath its
  31. sheltering branches? Why are you bothered by its uselessness?”
  32. (Geoffrey James, “The Tao of Programming”, 7.1)
  33. '''
  34. import curses
  35. import io
  36. import itertools
  37. CHARSET = '-\\|/'
  38. ENTERPRISE_CHARSET = CHARSET * 4 + '-._.-^' * 2
  39. class AbstractSpinner(object):
  40. '''The base class for all Spinners
  41. :param stream: file-like object with ``.write()`` method
  42. :param str charset: the sequence of characters to display
  43. The spinner should be used as follows:
  44. 1. exactly one call to :py:meth:`show()`
  45. 2. zero or more calls to :py:meth:`update()`
  46. 3. exactly one call to :py:meth:`hide()`
  47. '''
  48. def __init__(self, stream, charset=CHARSET):
  49. self.stream = stream
  50. self.charset = itertools.cycle(charset)
  51. def show(self, prompt):
  52. '''Show the spinner, with a prompt
  53. :param str prompt: prompt, like "please wait"
  54. '''
  55. raise NotImplementedError()
  56. def hide(self):
  57. '''Hide the spinner and the prompt'''
  58. raise NotImplementedError()
  59. def update(self):
  60. '''Show next spinner character'''
  61. raise NotImplementedError()
  62. class DummySpinner(AbstractSpinner):
  63. '''Dummy spinner, does not do anything'''
  64. def show(self, prompt):
  65. pass
  66. def hide(self):
  67. pass
  68. def update(self):
  69. pass
  70. class QubesSpinner(AbstractSpinner):
  71. '''Basic spinner
  72. This spinner uses standard ASCII control characters'''
  73. def __init__(self, *args, **kwargs):
  74. super(QubesSpinner, self).__init__(*args, **kwargs)
  75. self.hidelen = 0
  76. self.cub1 = '\b'
  77. def show(self, prompt):
  78. self.hidelen = len(prompt) + 2
  79. self.stream.write('{} {}'.format(prompt, next(self.charset)))
  80. self.stream.flush()
  81. def hide(self):
  82. self.stream.write('\r' + ' ' * self.hidelen + '\r')
  83. self.stream.flush()
  84. def update(self):
  85. self.stream.write(self.cub1 + next(self.charset))
  86. self.stream.flush()
  87. class QubesSpinnerEnterpriseEdition(QubesSpinner):
  88. '''Enterprise spinner
  89. This is tty- and terminfo-aware spinner. Recommended.
  90. '''
  91. def __init__(self, stream, charset=None):
  92. # our Enterprise logic follows
  93. self.stream_isatty = stream.isatty()
  94. if charset is None:
  95. charset = ENTERPRISE_CHARSET if self.stream_isatty else '.'
  96. super(QubesSpinnerEnterpriseEdition, self).__init__(stream, charset)
  97. if self.stream_isatty:
  98. try:
  99. curses.setupterm()
  100. self.has_terminfo = True
  101. self.cub1 = curses.tigetstr('cub1').decode()
  102. except (curses.error, io.UnsupportedOperation):
  103. # we are in very non-Enterprise environment
  104. self.has_terminfo = False
  105. else:
  106. self.cub1 = ''
  107. def hide(self):
  108. if self.stream_isatty:
  109. hideseq = '\r' + ' ' * self.hidelen + '\r'
  110. if self.has_terminfo:
  111. hideseq_l = (curses.tigetstr('cr'), curses.tigetstr('clr_eol'))
  112. if all(seq is not None for seq in hideseq_l):
  113. hideseq = ''.join(seq.decode() for seq in hideseq_l)
  114. else:
  115. hideseq = '\n'
  116. self.stream.write(hideseq)
  117. self.stream.flush()