~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/text.py

  • Committer: Martin Pool
  • Date: 2009-01-13 03:11:04 UTC
  • mto: This revision was merged to the branch mainline in revision 3937.
  • Revision ID: mbp@sourcefrog.net-20090113031104-03my054s02i9l2pe
Bump version to 1.12 and add news template

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005 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
20
20
"""
21
21
 
22
22
import sys
23
 
import time
24
 
import warnings
25
23
 
26
24
from bzrlib.lazy_import import lazy_import
27
25
lazy_import(globals(), """
30
28
from bzrlib import (
31
29
    progress,
32
30
    osutils,
33
 
    symbol_versioning,
34
31
    )
35
 
 
36
32
""")
37
33
 
38
34
from bzrlib.ui import CLIUIFactory
43
39
 
44
40
    def __init__(self,
45
41
                 bar_type=None,
46
 
                 stdin=None,
47
42
                 stdout=None,
48
43
                 stderr=None):
49
44
        """Create a TextUIFactory.
50
45
 
51
46
        :param bar_type: The type of progress bar to create. It defaults to 
52
47
                         letting the bzrlib.progress.ProgressBar factory auto
53
 
                         select.   Deprecated.
 
48
                         select.
54
49
        """
55
 
        super(TextUIFactory, self).__init__(stdin=stdin,
56
 
                stdout=stdout, stderr=stderr)
57
 
        if bar_type:
58
 
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 11, 0))
59
 
                % "bar_type parameter")
60
 
        # paints progress, network activity, etc
61
 
        self._progress_view = TextProgressView(self.stderr)
 
50
        super(TextUIFactory, self).__init__()
 
51
        self._bar_type = bar_type
 
52
        if stdout is None:
 
53
            self.stdout = sys.stdout
 
54
        else:
 
55
            self.stdout = stdout
 
56
        if stderr is None:
 
57
            self.stderr = sys.stderr
 
58
        else:
 
59
            self.stderr = stderr
62
60
 
63
61
    def prompt(self, prompt):
64
62
        """Emit prompt on the CLI."""
65
63
        self.stdout.write(prompt)
66
64
        
 
65
    def nested_progress_bar(self):
 
66
        """Return a nested progress bar.
 
67
        
 
68
        The actual bar type returned depends on the progress module which
 
69
        may return a tty or dots bar depending on the terminal.
 
70
        """
 
71
        if self._progress_bar_stack is None:
 
72
            self._progress_bar_stack = progress.ProgressBarStack(
 
73
                klass=self._bar_type)
 
74
        return self._progress_bar_stack.get_nested()
 
75
 
67
76
    def clear_term(self):
68
77
        """Prepare the terminal for output.
69
78
 
70
79
        This will, clear any progress bars, and leave the cursor at the
71
80
        leftmost position."""
72
 
        # XXX: If this is preparing to write to stdout, but that's for example
73
 
        # directed into a file rather than to the terminal, and the progress
74
 
        # bar _is_ going to the terminal, we shouldn't need
75
 
        # to clear it.  We might need to separately check for the case of 
76
 
        self._progress_view.clear()
77
 
 
78
 
    def note(self, msg):
79
 
        """Write an already-formatted message, clearing the progress bar if necessary."""
80
 
        self.clear_term()
81
 
        self.stdout.write(msg + '\n')
82
 
 
83
 
    def report_transport_activity(self, transport, byte_count, direction):
84
 
        """Called by transports as they do IO.
85
 
        
86
 
        This may update a progress bar, spinner, or similar display.
87
 
        By default it does nothing.
88
 
        """
89
 
        self._progress_view.show_transport_activity(byte_count)
90
 
 
91
 
    def _progress_updated(self, task):
92
 
        """A task has been updated and wants to be displayed.
93
 
        """
94
 
        if task != self._task_stack[-1]:
95
 
            warnings.warn("%r is not the top progress task %r" %
96
 
                (task, self._task_stack[-1]))
97
 
        self._progress_view.show_progress(task)
98
 
 
99
 
    def _progress_all_finished(self):
100
 
        self._progress_view.clear()
101
 
 
102
 
 
103
 
class TextProgressView(object):
104
 
    """Display of progress bar and other information on a tty.
105
 
    
106
 
    This shows one line of text, including possibly a network indicator, spinner, 
107
 
    progress bar, message, etc.
108
 
 
109
 
    One instance of this is created and held by the UI, and fed updates when a
110
 
    task wants to be painted.
111
 
 
112
 
    Transports feed data to this through the ui_factory object.
113
 
 
114
 
    The Progress views can comprise a tree with _parent_task pointers, but
115
 
    this only prints the stack from the nominated current task up to the root.
116
 
    """
117
 
 
118
 
    def __init__(self, term_file):
119
 
        self._term_file = term_file
120
 
        # true when there's output on the screen we may need to clear
121
 
        self._have_output = False
122
 
        # XXX: We could listen for SIGWINCH and update the terminal width...
123
 
        self._width = osutils.terminal_width()
124
 
        self._last_transport_msg = ''
125
 
        self._spin_pos = 0
126
 
        # time we last repainted the screen
127
 
        self._last_repaint = 0
128
 
        # time we last got information about transport activity
129
 
        self._transport_update_time = 0
130
 
        self._task_fraction = None
131
 
        self._last_task = None
132
 
        self._total_byte_count = 0
133
 
        self._bytes_since_update = 0
134
 
 
135
 
    def _show_line(self, s):
136
 
        n = self._width - 1
137
 
        self._term_file.write('\r%-*.*s\r' % (n, n, s))
138
 
 
139
 
    def clear(self):
140
 
        if self._have_output:
141
 
            self._show_line('')
142
 
        self._have_output = False
143
 
 
144
 
    def _render_bar(self):
145
 
        # return a string for the progress bar itself
146
 
        if (self._last_task is not None) and self._last_task.show_bar:
147
 
            spin_str =  r'/-\|'[self._spin_pos % 4]
148
 
            self._spin_pos += 1
149
 
            f = self._task_fraction or 0
150
 
            cols = 20
151
 
            # number of markers highlighted in bar
152
 
            markers = int(round(float(cols) * f)) - 1
153
 
            bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
154
 
            return bar_str
155
 
        elif (self._last_task is None) or self._last_task.show_spinner:
156
 
            spin_str =  r'/-\|'[self._spin_pos % 4]
157
 
            self._spin_pos += 1
158
 
            return spin_str + ' '
159
 
        else:
160
 
            return ''
161
 
 
162
 
    def _format_task(self, task):
163
 
        if not task.show_count:
164
 
            s = ''
165
 
        elif task.total_cnt is not None:
166
 
            s = ' %d/%d' % (task.current_cnt, task.total_cnt)
167
 
        elif task.current_cnt is not None:
168
 
            s = ' %d' % (task.current_cnt)
169
 
        else:
170
 
            s = ''
171
 
        self._task_fraction = task._overall_completion_fraction()
172
 
        # compose all the parent messages
173
 
        t = task
174
 
        m = task.msg
175
 
        while t._parent_task:
176
 
            t = t._parent_task
177
 
            if t.msg:
178
 
                m = t.msg + ':' + m
179
 
        return m + s
180
 
 
181
 
    def _repaint(self):
182
 
        bar_string = self._render_bar()
183
 
        if self._last_task:
184
 
            task_msg = self._format_task(self._last_task)
185
 
        else:
186
 
            task_msg = ''
187
 
        trans = self._last_transport_msg
188
 
        if trans and task_msg:
189
 
            trans += ' | '
190
 
        s = (bar_string
191
 
             + trans
192
 
             + task_msg
193
 
             )
194
 
        self._show_line(s)
195
 
        self._have_output = True
196
 
 
197
 
    def show_progress(self, task):
198
 
        self._last_task = task
199
 
        now = time.time()
200
 
        if now < self._last_repaint + 0.1:
 
81
        if self._progress_bar_stack is None:
201
82
            return
202
 
        if now > self._transport_update_time + 5:
203
 
            # no recent activity; expire it
204
 
            self._last_transport_msg = ''
205
 
        self._last_repaint = now
206
 
        self._repaint()
207
 
 
208
 
    def show_transport_activity(self, byte_count):
209
 
        """Called by transports as they do IO.
210
 
        
211
 
        This may update a progress bar, spinner, or similar display.
212
 
        By default it does nothing.
213
 
        """
214
 
        # XXX: Probably there should be a transport activity model, and that
215
 
        # too should be seen by the progress view, rather than being poked in
216
 
        # here.
217
 
        self._total_byte_count += byte_count
218
 
        self._bytes_since_update += byte_count
219
 
        now = time.time()
220
 
        if self._transport_update_time is None:
221
 
            self._transport_update_time = now
222
 
        elif now >= (self._transport_update_time + 0.2):
223
 
            # guard against clock stepping backwards, and don't update too
224
 
            # often
225
 
            rate = self._bytes_since_update / (now - self._transport_update_time)
226
 
            msg = ("%6dkB @ %4dkB/s" %
227
 
                (self._total_byte_count>>10, int(rate)>>10,))
228
 
            self._transport_update_time = now
229
 
            self._last_repaint = now
230
 
            self._bytes_since_update = 0
231
 
            self._last_transport_msg = msg
232
 
            self._repaint()
233
 
 
234
 
 
 
83
        overall_pb = self._progress_bar_stack.bottom()
 
84
        if overall_pb is not None:
 
85
            overall_pb.clear()