13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
18
"""Text UI, write output to the console.
25
import bzrlib.progress
26
from bzrlib.symbol_versioning import (deprecated_method,
28
from bzrlib.ui import CLIUIFactory
31
class TextUIFactory(CLIUIFactory):
27
from bzrlib.lazy_import import lazy_import
28
lazy_import(globals(), """
38
from bzrlib.ui import (
44
class TextUIFactory(UIFactory):
32
45
"""A UI factory for Text user interefaces."""
38
51
"""Create a TextUIFactory.
40
:param bar_type: The type of progress bar to create. It defaults to
41
letting the bzrlib.progress.ProgressBar factory auto
53
:param bar_type: The type of progress bar to create. Deprecated
54
and ignored; a TextProgressView is always used.
44
56
super(TextUIFactory, self).__init__()
45
self._bar_type = bar_type
47
self.stdout = sys.stdout
51
self.stderr = sys.stderr
55
def prompt(self, prompt):
56
"""Emit prompt on the CLI."""
57
self.stdout.write(prompt + "? [y/n]:")
57
# TODO: there's no good reason not to pass all three streams, maybe we
58
# should deprecate the default values...
62
# paints progress, network activity, etc
63
self._progress_view = self.make_progress_view()
59
@deprecated_method(zero_eight)
60
def progress_bar(self):
61
"""See UIFactory.nested_progress_bar()."""
62
# this in turn is abstract, and creates either a tty or dots
63
# bar depending on what we think of the terminal
64
return bzrlib.progress.ProgressBar()
66
"""Prepare the terminal for output.
68
This will, clear any progress bars, and leave the cursor at the
70
# XXX: If this is preparing to write to stdout, but that's for example
71
# directed into a file rather than to the terminal, and the progress
72
# bar _is_ going to the terminal, we shouldn't need
73
# to clear it. We might need to separately check for the case of
74
self._progress_view.clear()
76
def get_boolean(self, prompt):
78
self.prompt(prompt + "? [y/n]: ")
79
line = self.stdin.readline().lower()
80
if line in ('y\n', 'yes\n'):
82
elif line in ('n\n', 'no\n'):
84
elif line in ('', None):
85
# end-of-file; possibly should raise an error here instead
88
def get_non_echoed_password(self):
89
isatty = getattr(self.stdin, 'isatty', None)
90
if isatty is not None and isatty():
91
# getpass() ensure the password is not echoed and other
92
# cross-platform niceties
93
password = getpass.getpass('')
95
# echo doesn't make sense without a terminal
96
password = self.stdin.readline()
99
elif password[-1] == '\n':
100
password = password[:-1]
66
103
def get_password(self, prompt='', **kwargs):
67
104
"""Prompt the user for a password.
70
107
:param kwargs: Arguments which will be expanded into the prompt.
71
108
This lets front ends display different things if
73
:return: The password string, return None if the user
110
:return: The password string, return None if the user
74
111
canceled the request.
76
prompt = (prompt % kwargs).encode(sys.stdout.encoding, 'replace')
114
self.prompt(prompt, **kwargs)
78
115
# There's currently no way to say 'i decline to enter a password'
79
116
# as opposed to 'my password is empty' -- does it matter?
80
return getpass.getpass(prompt)
82
def nested_progress_bar(self):
83
"""Return a nested progress bar.
85
The actual bar type returned depends on the progress module which
86
may return a tty or dots bar depending on the terminal.
88
if self._progress_bar_stack is None:
89
self._progress_bar_stack = bzrlib.progress.ProgressBarStack(
91
return self._progress_bar_stack.get_nested()
94
"""Prepare the terminal for output.
96
This will, clear any progress bars, and leave the cursor at the
98
if self._progress_bar_stack is None:
100
overall_pb = self._progress_bar_stack.bottom()
101
if overall_pb is not None:
117
return self.get_non_echoed_password()
119
def get_username(self, prompt, **kwargs):
120
"""Prompt the user for a username.
122
:param prompt: The prompt to present the user
123
:param kwargs: Arguments which will be expanded into the prompt.
124
This lets front ends display different things if
126
:return: The username string, return None if the user
127
canceled the request.
130
self.prompt(prompt, **kwargs)
131
username = self.stdin.readline()
134
elif username[-1] == '\n':
135
username = username[:-1]
138
def make_progress_view(self):
139
"""Construct and return a new ProgressView subclass for this UI.
141
# if the user specifically requests either text or no progress bars,
142
# always do that. otherwise, guess based on $TERM and tty presence.
143
if os.environ.get('BZR_PROGRESS_BAR') == 'text':
144
return TextProgressView(self.stderr)
145
elif os.environ.get('BZR_PROGRESS_BAR') == 'none':
146
return NullProgressView()
147
elif progress._supports_progress(self.stderr):
148
return TextProgressView(self.stderr)
150
return NullProgressView()
153
"""Write an already-formatted message, clearing the progress bar if necessary."""
155
self.stdout.write(msg + '\n')
157
def prompt(self, prompt, **kwargs):
158
"""Emit prompt on the CLI.
160
:param kwargs: Dictionary of arguments to insert into the prompt,
161
to allow UIs to reformat the prompt.
164
# See <https://launchpad.net/bugs/365891>
165
prompt = prompt % kwargs
166
prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
168
self.stderr.write(prompt)
170
def report_transport_activity(self, transport, byte_count, direction):
171
"""Called by transports as they do IO.
173
This may update a progress bar, spinner, or similar display.
174
By default it does nothing.
176
self._progress_view.show_transport_activity(transport,
177
direction, byte_count)
179
def _progress_updated(self, task):
180
"""A task has been updated and wants to be displayed.
182
if not self._task_stack:
183
warnings.warn("%r updated but no tasks are active" %
185
elif task != self._task_stack[-1]:
186
warnings.warn("%r is not the top progress task %r" %
187
(task, self._task_stack[-1]))
188
self._progress_view.show_progress(task)
190
def _progress_all_finished(self):
191
self._progress_view.clear()
194
class TextProgressView(object):
195
"""Display of progress bar and other information on a tty.
197
This shows one line of text, including possibly a network indicator, spinner,
198
progress bar, message, etc.
200
One instance of this is created and held by the UI, and fed updates when a
201
task wants to be painted.
203
Transports feed data to this through the ui_factory object.
205
The Progress views can comprise a tree with _parent_task pointers, but
206
this only prints the stack from the nominated current task up to the root.
209
def __init__(self, term_file):
210
self._term_file = term_file
211
# true when there's output on the screen we may need to clear
212
self._have_output = False
213
# XXX: We could listen for SIGWINCH and update the terminal width...
214
# https://launchpad.net/bugs/316357
215
self._width = osutils.terminal_width()
216
self._last_transport_msg = ''
218
# time we last repainted the screen
219
self._last_repaint = 0
220
# time we last got information about transport activity
221
self._transport_update_time = 0
222
self._last_task = None
223
self._total_byte_count = 0
224
self._bytes_since_update = 0
227
def _show_line(self, s):
228
# sys.stderr.write("progress %r\n" % s)
230
self._term_file.write('\r%-*.*s\r' % (n, n, s))
233
if self._have_output:
235
self._have_output = False
237
def _render_bar(self):
238
# return a string for the progress bar itself
239
if (self._last_task is None) or self._last_task.show_bar:
240
# If there's no task object, we show space for the bar anyhow.
241
# That's because most invocations of bzr will end showing progress
242
# at some point, though perhaps only after doing some initial IO.
243
# It looks better to draw the progress bar initially rather than
244
# to have what looks like an incomplete progress bar.
245
spin_str = r'/-\|'[self._spin_pos % 4]
248
if self._last_task is None:
249
completion_fraction = 0
252
completion_fraction = \
253
self._last_task._overall_completion_fraction() or 0
254
if (completion_fraction < self._fraction and 'progress' in
256
import pdb;pdb.set_trace()
257
self._fraction = completion_fraction
258
markers = int(round(float(cols) * completion_fraction)) - 1
259
bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
261
elif self._last_task.show_spinner:
262
# The last task wanted just a spinner, no bar
263
spin_str = r'/-\|'[self._spin_pos % 4]
265
return spin_str + ' '
269
def _format_task(self, task):
270
if not task.show_count:
272
elif task.current_cnt is not None and task.total_cnt is not None:
273
s = ' %d/%d' % (task.current_cnt, task.total_cnt)
274
elif task.current_cnt is not None:
275
s = ' %d' % (task.current_cnt)
278
# compose all the parent messages
281
while t._parent_task:
287
def _render_line(self):
288
bar_string = self._render_bar()
290
task_msg = self._format_task(self._last_task)
293
if self._last_task and not self._last_task.show_transport_activity:
296
trans = self._last_transport_msg
299
return (bar_string + trans + task_msg)
302
s = self._render_line()
304
self._have_output = True
306
def show_progress(self, task):
307
"""Called by the task object when it has changed.
309
:param task: The top task object; its parents are also included
312
must_update = task is not self._last_task
313
self._last_task = task
315
if (not must_update) and (now < self._last_repaint + task.update_latency):
317
if now > self._transport_update_time + 10:
318
# no recent activity; expire it
319
self._last_transport_msg = ''
320
self._last_repaint = now
323
def show_transport_activity(self, transport, direction, byte_count):
324
"""Called by transports via the ui_factory, as they do IO.
326
This may update a progress bar, spinner, or similar display.
327
By default it does nothing.
329
# XXX: Probably there should be a transport activity model, and that
330
# too should be seen by the progress view, rather than being poked in
332
if not self._have_output:
333
# As a workaround for <https://launchpad.net/bugs/321935> we only
334
# show transport activity when there's already a progress bar
335
# shown, which time the application code is expected to know to
336
# clear off the progress bar when it's going to send some other
337
# output. Eventually it would be nice to have that automatically
340
self._total_byte_count += byte_count
341
self._bytes_since_update += byte_count
343
if self._total_byte_count < 2000:
344
# a little resistance at first, so it doesn't stay stuck at 0
345
# while connecting...
347
if self._transport_update_time is None:
348
self._transport_update_time = now
349
elif now >= (self._transport_update_time + 0.5):
350
# guard against clock stepping backwards, and don't update too
352
rate = self._bytes_since_update / (now - self._transport_update_time)
353
msg = ("%6dKB %5dKB/s" %
354
(self._total_byte_count>>10, int(rate)>>10,))
355
self._transport_update_time = now
356
self._last_repaint = now
357
self._bytes_since_update = 0
358
self._last_transport_msg = msg