~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/text.py

  • Committer: Vincent Ladeuil
  • Date: 2010-01-25 15:55:48 UTC
  • mto: (4985.1.4 add-attr-cleanup)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125155548-0l352pujvt5bzl5e
Deploy addAttrCleanup on the whole test suite.

Several use case worth mentioning:

- setting a module or any other object attribute is the majority
by far. In some cases the setting itself is deferred but most of
the time we want to set at the same time we add the cleanup.

- there multiple occurrences of protecting hooks or ui factory
which are now useless (the test framework takes care of that now),

- there was some lambda uses that can now be avoided.

That first cleanup already simplifies things a lot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005, 2008, 2009, 2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
"""Text UI, write output to the console.
19
19
"""
20
20
 
 
21
import codecs
 
22
import getpass
21
23
import os
22
24
import sys
23
25
import time
26
28
from bzrlib.lazy_import import lazy_import
27
29
lazy_import(globals(), """
28
30
from bzrlib import (
 
31
    debug,
29
32
    progress,
30
33
    osutils,
31
34
    symbol_versioning,
 
35
    trace,
32
36
    )
33
37
 
34
38
""")
47
51
                 stdout=None,
48
52
                 stderr=None):
49
53
        """Create a TextUIFactory.
50
 
 
51
 
        :param bar_type: The type of progress bar to create. It defaults to
52
 
                         letting the bzrlib.progress.ProgressBar factory auto
53
 
                         select.   Deprecated.
54
54
        """
55
55
        super(TextUIFactory, self).__init__()
56
56
        # TODO: there's no good reason not to pass all three streams, maybe we
61
61
        # paints progress, network activity, etc
62
62
        self._progress_view = self.make_progress_view()
63
63
        
 
64
    def be_quiet(self, state):
 
65
        if state and not self._quiet:
 
66
            self.clear_term()
 
67
        UIFactory.be_quiet(self, state)
 
68
        self._progress_view = self.make_progress_view()
 
69
 
64
70
    def clear_term(self):
65
71
        """Prepare the terminal for output.
66
72
 
84
90
                # end-of-file; possibly should raise an error here instead
85
91
                return None
86
92
 
 
93
    def get_integer(self, prompt):
 
94
        while True:
 
95
            self.prompt(prompt)
 
96
            line = self.stdin.readline()
 
97
            try:
 
98
                return int(line)
 
99
            except ValueError:
 
100
                pass
 
101
 
87
102
    def get_non_echoed_password(self):
88
103
        isatty = getattr(self.stdin, 'isatty', None)
89
104
        if isatty is not None and isatty():
137
152
    def make_progress_view(self):
138
153
        """Construct and return a new ProgressView subclass for this UI.
139
154
        """
140
 
        # if the user specifically requests either text or no progress bars,
141
 
        # always do that.  otherwise, guess based on $TERM and tty presence.
142
 
        if os.environ.get('BZR_PROGRESS_BAR') == 'text':
 
155
        # with --quiet, never any progress view
 
156
        # <https://bugs.edge.launchpad.net/bzr/+bug/320035>.  Otherwise if the
 
157
        # user specifically requests either text or no progress bars, always
 
158
        # do that.  otherwise, guess based on $TERM and tty presence.
 
159
        if self.is_quiet():
 
160
            return NullProgressView()
 
161
        elif os.environ.get('BZR_PROGRESS_BAR') == 'text':
143
162
            return TextProgressView(self.stderr)
144
163
        elif os.environ.get('BZR_PROGRESS_BAR') == 'none':
145
164
            return NullProgressView()
148
167
        else:
149
168
            return NullProgressView()
150
169
 
 
170
    def _make_output_stream_explicit(self, encoding, encoding_type):
 
171
        if encoding_type == 'exact':
 
172
            # force sys.stdout to be binary stream on win32; 
 
173
            # NB: this leaves the file set in that mode; may cause problems if
 
174
            # one process tries to do binary and then text output
 
175
            if sys.platform == 'win32':
 
176
                fileno = getattr(self.stdout, 'fileno', None)
 
177
                if fileno:
 
178
                    import msvcrt
 
179
                    msvcrt.setmode(fileno(), os.O_BINARY)
 
180
            return TextUIOutputStream(self, self.stdout)
 
181
        else:
 
182
            encoded_stdout = codecs.getwriter(encoding)(self.stdout,
 
183
                errors=encoding_type)
 
184
            # For whatever reason codecs.getwriter() does not advertise its encoding
 
185
            # it just returns the encoding of the wrapped file, which is completely
 
186
            # bogus. So set the attribute, so we can find the correct encoding later.
 
187
            encoded_stdout.encoding = encoding
 
188
            return TextUIOutputStream(self, encoded_stdout)
 
189
 
151
190
    def note(self, msg):
152
191
        """Write an already-formatted message, clearing the progress bar if necessary."""
153
192
        self.clear_term()
175
214
        self._progress_view.show_transport_activity(transport,
176
215
            direction, byte_count)
177
216
 
 
217
    def log_transport_activity(self, display=False):
 
218
        """See UIFactory.log_transport_activity()"""
 
219
        log = getattr(self._progress_view, 'log_transport_activity', None)
 
220
        if log is not None:
 
221
            log(display=display)
 
222
 
 
223
    def show_error(self, msg):
 
224
        self.clear_term()
 
225
        self.stderr.write("bzr: error: %s\n" % msg)
 
226
 
 
227
    def show_message(self, msg):
 
228
        self.note(msg)
 
229
 
 
230
    def show_warning(self, msg):
 
231
        self.clear_term()
 
232
        self.stderr.write("bzr: warning: %s\n" % msg)
 
233
 
178
234
    def _progress_updated(self, task):
179
235
        """A task has been updated and wants to be displayed.
180
236
        """
209
265
        self._term_file = term_file
210
266
        # true when there's output on the screen we may need to clear
211
267
        self._have_output = False
212
 
        # XXX: We could listen for SIGWINCH and update the terminal width...
213
 
        # https://launchpad.net/bugs/316357
214
 
        self._width = osutils.terminal_width()
215
268
        self._last_transport_msg = ''
216
269
        self._spin_pos = 0
217
270
        # time we last repainted the screen
221
274
        self._last_task = None
222
275
        self._total_byte_count = 0
223
276
        self._bytes_since_update = 0
 
277
        self._bytes_by_direction = {'unknown': 0, 'read': 0, 'write': 0}
 
278
        self._first_byte_time = None
 
279
        self._fraction = 0
 
280
        # force the progress bar to be off, as at the moment it doesn't 
 
281
        # correspond reliably to overall command progress
 
282
        self.enable_bar = False
224
283
 
225
284
    def _show_line(self, s):
226
 
        n = self._width - 1
227
 
        self._term_file.write('\r%-*.*s\r' % (n, n, s))
 
285
        # sys.stderr.write("progress %r\n" % s)
 
286
        width = osutils.terminal_width()
 
287
        if width is not None:
 
288
            # we need one extra space for terminals that wrap on last char
 
289
            width = width - 1
 
290
            s = '%-*.*s' % (width, width, s)
 
291
        self._term_file.write('\r' + s + '\r')
228
292
 
229
293
    def clear(self):
230
294
        if self._have_output:
233
297
 
234
298
    def _render_bar(self):
235
299
        # return a string for the progress bar itself
236
 
        if (self._last_task is None) or self._last_task.show_bar:
 
300
        if self.enable_bar and (
 
301
            (self._last_task is None) or self._last_task.show_bar):
237
302
            # If there's no task object, we show space for the bar anyhow.
238
303
            # That's because most invocations of bzr will end showing progress
239
304
            # at some point, though perhaps only after doing some initial IO.
244
309
            cols = 20
245
310
            if self._last_task is None:
246
311
                completion_fraction = 0
 
312
                self._fraction = 0
247
313
            else:
248
314
                completion_fraction = \
249
315
                    self._last_task._overall_completion_fraction() or 0
 
316
            if (completion_fraction < self._fraction and 'progress' in
 
317
                debug.debug_flags):
 
318
                import pdb;pdb.set_trace()
 
319
            self._fraction = completion_fraction
250
320
            markers = int(round(float(cols) * completion_fraction)) - 1
251
321
            bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
252
322
            return bar_str
253
 
        elif self._last_task.show_spinner:
 
323
        elif (self._last_task is None) or self._last_task.show_spinner:
254
324
            # The last task wanted just a spinner, no bar
255
325
            spin_str =  r'/-\|'[self._spin_pos % 4]
256
326
            self._spin_pos += 1
282
352
            task_msg = self._format_task(self._last_task)
283
353
        else:
284
354
            task_msg = ''
285
 
        trans = self._last_transport_msg
286
 
        if trans:
287
 
            trans += ' | '
 
355
        if self._last_task and not self._last_task.show_transport_activity:
 
356
            trans = ''
 
357
        else:
 
358
            trans = self._last_transport_msg
 
359
            if trans:
 
360
                trans += ' | '
288
361
        return (bar_string + trans + task_msg)
289
362
 
290
363
    def _repaint(self):
301
374
        must_update = task is not self._last_task
302
375
        self._last_task = task
303
376
        now = time.time()
304
 
        if (not must_update) and (now < self._last_repaint + 0.1):
 
377
        if (not must_update) and (now < self._last_repaint + task.update_latency):
305
378
            return
306
379
        if now > self._transport_update_time + 10:
307
380
            # no recent activity; expire it
315
388
        This may update a progress bar, spinner, or similar display.
316
389
        By default it does nothing.
317
390
        """
318
 
        # XXX: Probably there should be a transport activity model, and that
319
 
        # too should be seen by the progress view, rather than being poked in
320
 
        # here.
321
 
        if not self._have_output:
322
 
            # As a workaround for <https://launchpad.net/bugs/321935> we only
323
 
            # show transport activity when there's already a progress bar
324
 
            # shown, which time the application code is expected to know to
325
 
            # clear off the progress bar when it's going to send some other
326
 
            # output.  Eventually it would be nice to have that automatically
327
 
            # synchronized.
328
 
            return
 
391
        # XXX: there should be a transport activity model, and that too should
 
392
        #      be seen by the progress view, rather than being poked in here.
329
393
        self._total_byte_count += byte_count
330
394
        self._bytes_since_update += byte_count
 
395
        if self._first_byte_time is None:
 
396
            # Note that this isn't great, as technically it should be the time
 
397
            # when the bytes started transferring, not when they completed.
 
398
            # However, we usually start with a small request anyway.
 
399
            self._first_byte_time = time.time()
 
400
        if direction in self._bytes_by_direction:
 
401
            self._bytes_by_direction[direction] += byte_count
 
402
        else:
 
403
            self._bytes_by_direction['unknown'] += byte_count
 
404
        if 'no_activity' in debug.debug_flags:
 
405
            # Can be used as a workaround if
 
406
            # <https://launchpad.net/bugs/321935> reappears and transport
 
407
            # activity is cluttering other output.  However, thanks to
 
408
            # TextUIOutputStream this shouldn't be a problem any more.
 
409
            return
331
410
        now = time.time()
 
411
        if self._total_byte_count < 2000:
 
412
            # a little resistance at first, so it doesn't stay stuck at 0
 
413
            # while connecting...
 
414
            return
332
415
        if self._transport_update_time is None:
333
416
            self._transport_update_time = now
334
417
        elif now >= (self._transport_update_time + 0.5):
342
425
            self._bytes_since_update = 0
343
426
            self._last_transport_msg = msg
344
427
            self._repaint()
 
428
 
 
429
    def _format_bytes_by_direction(self):
 
430
        if self._first_byte_time is None:
 
431
            bps = 0.0
 
432
        else:
 
433
            transfer_time = time.time() - self._first_byte_time
 
434
            if transfer_time < 0.001:
 
435
                transfer_time = 0.001
 
436
            bps = self._total_byte_count / transfer_time
 
437
 
 
438
        msg = ('Transferred: %.0fKiB'
 
439
               ' (%.1fK/s r:%.0fK w:%.0fK'
 
440
               % (self._total_byte_count / 1024.,
 
441
                  bps / 1024.,
 
442
                  self._bytes_by_direction['read'] / 1024.,
 
443
                  self._bytes_by_direction['write'] / 1024.,
 
444
                 ))
 
445
        if self._bytes_by_direction['unknown'] > 0:
 
446
            msg += ' u:%.0fK)' % (
 
447
                self._bytes_by_direction['unknown'] / 1024.
 
448
                )
 
449
        else:
 
450
            msg += ')'
 
451
        return msg
 
452
 
 
453
    def log_transport_activity(self, display=False):
 
454
        msg = self._format_bytes_by_direction()
 
455
        trace.mutter(msg)
 
456
        if display and self._total_byte_count > 0:
 
457
            self.clear()
 
458
            self._term_file.write(msg + '\n')
 
459
 
 
460
 
 
461
class TextUIOutputStream(object):
 
462
    """Decorates an output stream so that the terminal is cleared before writing.
 
463
 
 
464
    This is supposed to ensure that the progress bar does not conflict with bulk
 
465
    text output.
 
466
    """
 
467
    # XXX: this does not handle the case of writing part of a line, then doing
 
468
    # progress bar output: the progress bar will probably write over it.
 
469
    # one option is just to buffer that text until we have a full line;
 
470
    # another is to save and restore it
 
471
 
 
472
    # XXX: might need to wrap more methods
 
473
 
 
474
    def __init__(self, ui_factory, wrapped_stream):
 
475
        self.ui_factory = ui_factory
 
476
        self.wrapped_stream = wrapped_stream
 
477
        # this does no transcoding, but it must expose the underlying encoding
 
478
        # because some callers need to know what can be written - see for
 
479
        # example unescape_for_display.
 
480
        self.encoding = getattr(wrapped_stream, 'encoding', None)
 
481
 
 
482
    def flush(self):
 
483
        self.ui_factory.clear_term()
 
484
        self.wrapped_stream.flush()
 
485
 
 
486
    def write(self, to_write):
 
487
        self.ui_factory.clear_term()
 
488
        self.wrapped_stream.write(to_write)
 
489
 
 
490
    def writelines(self, lines):
 
491
        self.ui_factory.clear_term()
 
492
        self.wrapped_stream.writelines(lines)