/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5752.3.8 by John Arbash Meinel
Merge bzr.dev 5764 to resolve release-notes (aka NEWS) conflicts
1
# Copyright (C) 2005-2011 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.49.21 by John Arbash Meinel
Refactored bzrlib/ui.py into a module with the possibility for multiple ui forms.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.49.21 by John Arbash Meinel
Refactored bzrlib/ui.py into a module with the possibility for multiple ui forms.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.49.21 by John Arbash Meinel
Refactored bzrlib/ui.py into a module with the possibility for multiple ui forms.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.49.21 by John Arbash Meinel
Refactored bzrlib/ui.py into a module with the possibility for multiple ui forms.
16
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
17
"""Text UI, write output to the console."""
1185.49.21 by John Arbash Meinel
Refactored bzrlib/ui.py into a module with the possibility for multiple ui forms.
18
6379.6.1 by Jelmer Vernooij
Import absolute_import in a few places.
19
from __future__ import absolute_import
20
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
21
import codecs
4449.2.1 by Martin Pool
TextUIFactory now respects BZR_PROGRESS_BAR again
22
import os
1996.3.27 by John Arbash Meinel
lazy import getpass in bzrlib.ui.text
23
import sys
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
24
import warnings
1996.3.27 by John Arbash Meinel
lazy import getpass in bzrlib.ui.text
25
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
26
from ..lazy_import import lazy_import
1996.3.27 by John Arbash Meinel
lazy import getpass in bzrlib.ui.text
27
lazy_import(globals(), """
5753.2.2 by Jelmer Vernooij
Remove some unnecessary imports, clean up lazy imports.
28
import getpass
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
29
import time
5753.2.2 by Jelmer Vernooij
Remove some unnecessary imports, clean up lazy imports.
30
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
31
from breezy import (
4332.3.18 by Robert Collins
Add -Dprogress to assist in debugging progress bar jumping.
32
    debug,
1996.3.27 by John Arbash Meinel
lazy import getpass in bzrlib.ui.text
33
    progress,
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
34
    )
35
""")
36
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
37
from .. import (
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
38
    config,
2294.4.1 by Vincent Ladeuil
Add a UIFactory.get_login method, fix tests.
39
    osutils,
4906.1.1 by John Arbash Meinel
Basic implementation of logging bytes transferred when bzr exits.
40
    trace,
1996.3.27 by John Arbash Meinel
lazy import getpass in bzrlib.ui.text
41
    )
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
42
from . import (
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
43
    NullProgressView,
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
44
    UIFactory,
4449.3.15 by Martin Pool
Move NullProgressView and make_progress_view up to UIFactory base class
45
    )
1687.1.4 by Robert Collins
Add bzrlib.ui.ui_factory.get_boolean().
46
47
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
48
class _ChooseUI(object):
49
50
    """ Helper class for choose implementation.
51
    """
52
53
    def __init__(self, ui, msg, choices, default):
54
        self.ui = ui
55
        self._setup_mode()
56
        self._build_alternatives(msg, choices, default)
57
58
    def _setup_mode(self):
59
        """Setup input mode (line-based, char-based) and echo-back.
60
6622.1.28 by Jelmer Vernooij
More renames; commands in output, environment variables.
61
        Line-based input is used if the BRZ_TEXTUI_INPUT environment
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
62
        variable is set to 'line-based', or if there is no controlling
63
        terminal.
64
        """
6622.1.28 by Jelmer Vernooij
More renames; commands in output, environment variables.
65
        if os.environ.get('BRZ_TEXTUI_INPUT') != 'line-based' and \
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
66
           self.ui.stdin == sys.stdin and self.ui.stdin.isatty():
67
            self.line_based = False
68
            self.echo_back = True
69
        else:
70
            self.line_based = True
71
            self.echo_back = not self.ui.stdin.isatty()
72
73
    def _build_alternatives(self, msg, choices, default):
74
        """Parse choices string.
75
76
        Setup final prompt and the lists of choices and associated
77
        shortcuts.
78
        """
79
        index = 0
80
        help_list = []
81
        self.alternatives = {}
82
        choices = choices.split('\n')
83
        if default is not None and default not in range(0, len(choices)):
84
            raise ValueError("invalid default index")
85
        for c in choices:
86
            name = c.replace('&', '').lower()
87
            choice = (name, index)
88
            if name in self.alternatives:
89
                raise ValueError("duplicated choice: %s" % name)
90
            self.alternatives[name] = choice
91
            shortcut = c.find('&')
92
            if -1 != shortcut and (shortcut + 1) < len(c):
93
                help = c[:shortcut]
94
                help += '[' + c[shortcut + 1] + ']'
95
                help += c[(shortcut + 2):]
96
                shortcut = c[shortcut + 1]
97
            else:
98
                c = c.replace('&', '')
99
                shortcut = c[0]
100
                help = '[%s]%s' % (shortcut, c[1:])
101
            shortcut = shortcut.lower()
102
            if shortcut in self.alternatives:
103
                raise ValueError("duplicated shortcut: %s" % shortcut)
104
            self.alternatives[shortcut] = choice
105
            # Add redirections for default.
106
            if index == default:
107
                self.alternatives[''] = choice
108
                self.alternatives['\r'] = choice
109
            help_list.append(help)
110
            index += 1
111
112
        self.prompt = u'%s (%s): ' % (msg, ', '.join(help_list))
113
114
    def _getline(self):
115
        line = self.ui.stdin.readline()
116
        if '' == line:
117
            raise EOFError
118
        return line.strip()
119
120
    def _getchar(self):
121
        char = osutils.getchar()
122
        if char == chr(3): # INTR
123
            raise KeyboardInterrupt
124
        if char == chr(4): # EOF (^d, C-d)
125
            raise EOFError
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
126
        return char.decode("ascii", "replace")
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
127
128
    def interact(self):
129
        """Keep asking the user until a valid choice is made.
130
        """
131
        if self.line_based:
132
            getchoice = self._getline
133
        else:
134
            getchoice = self._getchar
135
        iter = 0
136
        while True:
137
            iter += 1
138
            if 1 == iter or self.line_based:
139
                self.ui.prompt(self.prompt)
140
            try:
141
                choice = getchoice()
142
            except EOFError:
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
143
                self.ui.stderr.write(u'\n')
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
144
                return None
145
            except KeyboardInterrupt:
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
146
                self.ui.stderr.write(u'\n')
147
                raise
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
148
            choice = choice.lower()
149
            if choice not in self.alternatives:
150
                # Not a valid choice, keep on asking.
151
                continue
152
            name, index = self.alternatives[choice]
153
            if self.echo_back:
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
154
                self.ui.stderr.write(name + u'\n')
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
155
            return index
156
157
6561.2.1 by Vincent Ladeuil
Add a ``progress_bar`` config option.
158
opt_progress_bar = config.Option(
159
    'progress_bar', help='Progress bar type.',
6622.1.28 by Jelmer Vernooij
More renames; commands in output, environment variables.
160
    default_from_env=['BRZ_PROGRESS_BAR'], default=None,
6561.2.1 by Vincent Ladeuil
Add a ``progress_bar`` config option.
161
    invalid='error')
162
163
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
164
class TextUIFactory(UIFactory):
6561.2.1 by Vincent Ladeuil
Add a ``progress_bar`` config option.
165
    """A UI factory for Text user interfaces."""
1681.1.2 by Robert Collins
* bzrlib.ui.text.TextUIFactory now accepts a bar_type parameter which
166
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
167
    def __init__(self, stdin, stdout, stderr):
168
        """Create a TextUIFactory."""
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
169
        super(TextUIFactory, self).__init__()
170
        self.stdin = stdin
171
        self.stdout = stdout
172
        self.stderr = stderr
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
173
        # paints progress, network activity, etc
4449.3.15 by Martin Pool
Move NullProgressView and make_progress_view up to UIFactory base class
174
        self._progress_view = self.make_progress_view()
4797.20.2 by
Register SIGWINCH only when creating a TextUIFactory
175
6182.2.13 by Benoît Pierre
Rename ui.confirm to ui.choose.
176
    def choose(self, msg, choices, default=None):
6182.2.11 by Benoît Pierre
Small tweak to TextUIFactory.confirm documentation.
177
        """Prompt the user for a list of alternatives.
6182.2.2 by Benoît Pierre
Implement TextUIFactory.confirm.
178
6182.2.14 by Benoît Pierre
Rework TextUIFactory.choose again to make the code simpler to follow.
179
        Support both line-based and char-based editing.
6182.2.2 by Benoît Pierre
Implement TextUIFactory.confirm.
180
181
        In line-based mode, both the shortcut and full choice name are valid
6182.2.13 by Benoît Pierre
Rename ui.confirm to ui.choose.
182
        answers, e.g. for choose('prompt', '&yes\n&no'): 'y', ' Y ', ' yes',
6182.2.2 by Benoît Pierre
Implement TextUIFactory.confirm.
183
        'YES ' are all valid input lines for choosing 'yes'.
184
185
        An empty line, when in line-based mode, or pressing enter in char-based
186
        mode will select the default choice (if any).
187
188
        Choice is echoed back if:
189
        - input is char-based; which means a controlling terminal is available,
190
          and osutils.getchar is used
191
        - input is line-based, and no controlling terminal is available
192
        """
6182.2.14 by Benoît Pierre
Rework TextUIFactory.choose again to make the code simpler to follow.
193
6182.2.28 by Benoît Pierre
Cleanup helper class for TextUIFactory.choose.
194
        choose_ui = _ChooseUI(self, msg, choices, default)
6182.2.14 by Benoît Pierre
Rework TextUIFactory.choose again to make the code simpler to follow.
195
        return choose_ui.interact()
6182.2.2 by Benoît Pierre
Implement TextUIFactory.confirm.
196
4961.1.2 by Martin Pool
quietness-state is now tracked on UIFactory
197
    def be_quiet(self, state):
198
        if state and not self._quiet:
199
            self.clear_term()
200
        UIFactory.be_quiet(self, state)
4961.1.3 by Martin Pool
trace quietness now controls whether the progress bar appears
201
        self._progress_view = self.make_progress_view()
4961.1.2 by Martin Pool
quietness-state is now tracked on UIFactory
202
1558.8.1 by Aaron Bentley
Fix overall progress bar's interaction with 'note' and 'warning'
203
    def clear_term(self):
204
        """Prepare the terminal for output.
205
206
        This will, clear any progress bars, and leave the cursor at the
207
        leftmost position."""
3882.7.6 by Martin Pool
Preliminary support for drawing network io into the progress bar
208
        # XXX: If this is preparing to write to stdout, but that's for example
209
        # directed into a file rather than to the terminal, and the progress
210
        # bar _is_ going to the terminal, we shouldn't need
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
211
        # to clear it.  We might need to separately check for the case of
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
212
        self._progress_view.clear()
3882.7.5 by Martin Pool
Further mockup of transport-based activity indicator.
213
4597.3.37 by Vincent Ladeuil
Allows ui factories to query users for an integer.
214
    def get_integer(self, prompt):
215
        while True:
216
            self.prompt(prompt)
217
            line = self.stdin.readline()
218
            try:
219
                return int(line)
220
            except ValueError:
221
                pass
222
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
223
    def get_non_echoed_password(self):
224
        isatty = getattr(self.stdin, 'isatty', None)
225
        if isatty is not None and isatty():
226
            # getpass() ensure the password is not echoed and other
227
            # cross-platform niceties
228
            password = getpass.getpass('')
229
        else:
230
            # echo doesn't make sense without a terminal
231
            password = self.stdin.readline()
232
            if not password:
233
                password = None
6559.2.1 by Vincent Ladeuil
Makes AuthenticationConfig always return unicode user names and passwords.
234
            else:
235
                if password[-1] == '\n':
236
                    password = password[:-1]
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
237
        return password
238
5863.6.1 by Jelmer Vernooij
Require a unicode prompt to be passed into all methods that prompt.
239
    def get_password(self, prompt=u'', **kwargs):
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
240
        """Prompt the user for a password.
241
242
        :param prompt: The prompt to present the user
243
        :param kwargs: Arguments which will be expanded into the prompt.
244
                       This lets front ends display different things if
245
                       they so choose.
246
        :return: The password string, return None if the user
247
                 canceled the request.
248
        """
249
        prompt += ': '
250
        self.prompt(prompt, **kwargs)
251
        # There's currently no way to say 'i decline to enter a password'
252
        # as opposed to 'my password is empty' -- does it matter?
253
        return self.get_non_echoed_password()
254
255
    def get_username(self, prompt, **kwargs):
256
        """Prompt the user for a username.
257
258
        :param prompt: The prompt to present the user
259
        :param kwargs: Arguments which will be expanded into the prompt.
260
                       This lets front ends display different things if
261
                       they so choose.
262
        :return: The username string, return None if the user
263
                 canceled the request.
264
        """
265
        prompt += ': '
266
        self.prompt(prompt, **kwargs)
267
        username = self.stdin.readline()
268
        if not username:
269
            username = None
6559.2.1 by Vincent Ladeuil
Makes AuthenticationConfig always return unicode user names and passwords.
270
        else:
271
            if username[-1] == '\n':
272
                username = username[:-1]
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
273
        return username
274
4449.3.15 by Martin Pool
Move NullProgressView and make_progress_view up to UIFactory base class
275
    def make_progress_view(self):
276
        """Construct and return a new ProgressView subclass for this UI.
277
        """
4961.1.2 by Martin Pool
quietness-state is now tracked on UIFactory
278
        # with --quiet, never any progress view
5243.1.2 by Martin
Point launchpad links in comments at production server rather than edge
279
        # <https://bugs.launchpad.net/bzr/+bug/320035>.  Otherwise if the
4961.1.2 by Martin Pool
quietness-state is now tracked on UIFactory
280
        # user specifically requests either text or no progress bars, always
281
        # do that.  otherwise, guess based on $TERM and tty presence.
282
        if self.is_quiet():
283
            return NullProgressView()
6561.2.1 by Vincent Ladeuil
Add a ``progress_bar`` config option.
284
        pb_type = config.GlobalStack().get('progress_bar')
285
        if pb_type == 'none': # Explicit requirement
286
            return NullProgressView()
287
        if (pb_type == 'text' # Explicit requirement
288
            or progress._supports_progress(self.stderr)): # Guess
289
            return TextProgressView(self.stderr)
290
        # No explicit requirement and no successful guess
291
        return NullProgressView()
4449.2.1 by Martin Pool
TextUIFactory now respects BZR_PROGRESS_BAR again
292
4792.8.5 by Martin Pool
Support encoding_type=exact for make_output_stream
293
    def _make_output_stream_explicit(self, encoding, encoding_type):
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
294
        return TextUIOutputStream(self, self.stdout, encoding, encoding_type)
4792.8.2 by Martin Pool
New method ui_factory.make_output_stream
295
3882.8.4 by Martin Pool
All UI factories should support note()
296
    def note(self, msg):
297
        """Write an already-formatted message, clearing the progress bar if necessary."""
298
        self.clear_term()
299
        self.stdout.write(msg + '\n')
300
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
301
    def prompt(self, prompt, **kwargs):
302
        """Emit prompt on the CLI.
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
303
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
304
        :param kwargs: Dictionary of arguments to insert into the prompt,
305
            to allow UIs to reformat the prompt.
306
        """
6619.3.18 by Jelmer Vernooij
Run 2to3 idioms fixer.
307
        if not isinstance(prompt, unicode):
5863.6.1 by Jelmer Vernooij
Require a unicode prompt to be passed into all methods that prompt.
308
            raise ValueError("prompt %r not a unicode string" % prompt)
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
309
        if kwargs:
310
            # See <https://launchpad.net/bugs/365891>
311
            prompt = prompt % kwargs
312
        self.clear_term()
6182.2.10 by Benoît Pierre
Flush stdout before prompting in TextUIFactory.
313
        self.stdout.flush()
4449.3.18 by Martin Pool
Fuse CLIUIFactory and TextUIFactory and deprecate the old name
314
        self.stderr.write(prompt)
315
3882.7.5 by Martin Pool
Further mockup of transport-based activity indicator.
316
    def report_transport_activity(self, transport, byte_count, direction):
317
        """Called by transports as they do IO.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
318
3882.7.5 by Martin Pool
Further mockup of transport-based activity indicator.
319
        This may update a progress bar, spinner, or similar display.
320
        By default it does nothing.
321
        """
4449.2.1 by Martin Pool
TextUIFactory now respects BZR_PROGRESS_BAR again
322
        self._progress_view.show_transport_activity(transport,
4110.2.19 by Martin Pool
Transport activity now shows scheme and direction
323
            direction, byte_count)
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
324
4906.1.1 by John Arbash Meinel
Basic implementation of logging bytes transferred when bzr exits.
325
    def log_transport_activity(self, display=False):
326
        """See UIFactory.log_transport_activity()"""
327
        log = getattr(self._progress_view, 'log_transport_activity', None)
328
        if log is not None:
329
            log(display=display)
330
4711.1.7 by Martin Pool
Add UIFactory.show_error, show_warning, show_message
331
    def show_error(self, msg):
332
        self.clear_term()
333
        self.stderr.write("bzr: error: %s\n" % msg)
334
4711.1.8 by Martin Pool
Add show_warning and show_message tests and implementations
335
    def show_message(self, msg):
336
        self.note(msg)
337
338
    def show_warning(self, msg):
339
        self.clear_term()
340
        self.stderr.write("bzr: warning: %s\n" % msg)
341
3948.2.3 by Martin Pool
Make the interface from ProgressTask to ui more private
342
    def _progress_updated(self, task):
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
343
        """A task has been updated and wants to be displayed.
344
        """
4070.1.1 by Martin Pool
Be more robust about pb updates when none are active
345
        if not self._task_stack:
346
            warnings.warn("%r updated but no tasks are active" %
347
                (task,))
348
        elif task != self._task_stack[-1]:
4961.2.19 by Martin Pool
Suppress un-helpful warning about progress task ordering
349
            # We used to check it was the top task, but it's hard to always
350
            # get this right and it's not necessarily useful: any actual
351
            # problems will be evident in use
352
            #warnings.warn("%r is not the top progress task %r" %
353
            #     (task, self._task_stack[-1]))
354
            pass
3882.7.7 by Martin Pool
Change progress bars to a more MVC style
355
        self._progress_view.show_progress(task)
356
3948.2.5 by Martin Pool
rename to _progress_all_finished
357
    def _progress_all_finished(self):
3948.2.3 by Martin Pool
Make the interface from ProgressTask to ui more private
358
        self._progress_view.clear()
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
359
4634.144.8 by Martin Pool
Generalize to ui_factory.show_user_warning
360
    def show_user_warning(self, warning_id, **message_args):
4634.144.5 by Martin Pool
Cleaner presentation and tests for warn_cross_format_fetch
361
        """Show a text message to the user.
362
363
        Explicitly not for warnings about bzr apis, deprecations or internals.
364
        """
365
        # eventually trace.warning should migrate here, to avoid logging and
366
        # be easier to test; that has a lot of test fallout so for now just
367
        # new code can call this
4634.144.11 by Martin Pool
Rename squelched to suppressed
368
        if warning_id not in self.suppressed_warnings:
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
369
            warning = self.format_user_warning(warning_id, message_args)
370
            self.stderr.write(warning + '\n')
371
372
373
def pad_to_width(line, width, encoding_hint='ascii'):
374
    """Truncate or pad unicode line to width.
375
376
    This is best-effort for now, and strings containing control codes or
377
    non-ascii text may be cut and padded incorrectly.
378
    """
379
    s = line.encode(encoding_hint, 'replace')
6621.2.26 by Martin
Misc set of changes to get started with selftest on Python 3
380
    return (b'%-*.*s' % (width, width, s)).decode(encoding_hint)
4634.144.5 by Martin Pool
Cleaner presentation and tests for warn_cross_format_fetch
381
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
382
383
class TextProgressView(object):
384
    """Display of progress bar and other information on a tty.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
385
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
386
    This shows one line of text, including possibly a network indicator,
387
    spinner, progress bar, message, etc.
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
388
389
    One instance of this is created and held by the UI, and fed updates when a
390
    task wants to be painted.
391
392
    Transports feed data to this through the ui_factory object.
3948.2.2 by Martin Pool
Corrections to finishing progress bars
393
394
    The Progress views can comprise a tree with _parent_task pointers, but
395
    this only prints the stack from the nominated current task up to the root.
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
396
    """
397
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
398
    def __init__(self, term_file, encoding=None, errors=None):
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
399
        self._term_file = term_file
6437.57.3 by Martin Packman
Make TextProgressView somewhat aware of encodings
400
        if encoding is None:
6437.57.7 by Martin Packman
Correct and test fallback to ascii logic when a stream has no encoding
401
            self._encoding = getattr(term_file, "encoding", None) or "ascii"
6437.57.3 by Martin Packman
Make TextProgressView somewhat aware of encodings
402
        else:
403
            self._encoding = encoding
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
404
        # true when there's output on the screen we may need to clear
405
        self._have_output = False
406
        self._last_transport_msg = ''
407
        self._spin_pos = 0
408
        # time we last repainted the screen
409
        self._last_repaint = 0
410
        # time we last got information about transport activity
411
        self._transport_update_time = 0
412
        self._last_task = None
413
        self._total_byte_count = 0
414
        self._bytes_since_update = 0
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
415
        self._bytes_by_direction = {'unknown': 0, 'read': 0, 'write': 0}
4906.1.5 by John Arbash Meinel
Include the KiB/s for the transfer.
416
        self._first_byte_time = None
4332.3.18 by Robert Collins
Add -Dprogress to assist in debugging progress bar jumping.
417
        self._fraction = 0
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
418
        # force the progress bar to be off, as at the moment it doesn't
4880.2.1 by Martin Pool
Text progress view is now only a spinner not a bar.
419
        # correspond reliably to overall command progress
420
        self.enable_bar = False
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
421
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
422
    def _avail_width(self):
423
        # we need one extra space for terminals that wrap on last char
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
424
        w = osutils.terminal_width()
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
425
        if w is None:
5050.16.1 by Martin Pool
Clear off progress bars by painting spaces.
426
            return None
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
427
        else:
428
            return w - 1
429
6437.57.3 by Martin Packman
Make TextProgressView somewhat aware of encodings
430
    def _show_line(self, u):
5050.16.1 by Martin Pool
Clear off progress bars by painting spaces.
431
        width = self._avail_width()
432
        if width is not None:
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
433
            u = pad_to_width(u, width, encoding_hint=self._encoding)
434
        self._term_file.write('\r' + u + '\r')
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
435
436
    def clear(self):
437
        if self._have_output:
438
            self._show_line('')
439
        self._have_output = False
440
441
    def _render_bar(self):
442
        # return a string for the progress bar itself
4880.2.1 by Martin Pool
Text progress view is now only a spinner not a bar.
443
        if self.enable_bar and (
444
            (self._last_task is None) or self._last_task.show_bar):
4103.3.3 by Martin Pool
Show the progress bar part when showing activity by default
445
            # If there's no task object, we show space for the bar anyhow.
446
            # That's because most invocations of bzr will end showing progress
447
            # at some point, though perhaps only after doing some initial IO.
448
            # It looks better to draw the progress bar initially rather than
449
            # to have what looks like an incomplete progress bar.
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
450
            spin_str =  r'/-\|'[self._spin_pos % 4]
451
            self._spin_pos += 1
452
            cols = 20
4110.2.19 by Martin Pool
Transport activity now shows scheme and direction
453
            if self._last_task is None:
454
                completion_fraction = 0
4332.3.18 by Robert Collins
Add -Dprogress to assist in debugging progress bar jumping.
455
                self._fraction = 0
4110.2.19 by Martin Pool
Transport activity now shows scheme and direction
456
            else:
457
                completion_fraction = \
458
                    self._last_task._overall_completion_fraction() or 0
4332.3.18 by Robert Collins
Add -Dprogress to assist in debugging progress bar jumping.
459
            if (completion_fraction < self._fraction and 'progress' in
460
                debug.debug_flags):
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
461
                debug.set_trace()
4332.3.18 by Robert Collins
Add -Dprogress to assist in debugging progress bar jumping.
462
            self._fraction = completion_fraction
4110.2.15 by Martin Pool
Fix bug in showing task progress and add a test
463
            markers = int(round(float(cols) * completion_fraction)) - 1
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
464
            bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
465
            return bar_str
4912.1.1 by Martin Pool
Transport activity indicator is now shown even if there's no pb
466
        elif (self._last_task is None) or self._last_task.show_spinner:
4103.3.3 by Martin Pool
Show the progress bar part when showing activity by default
467
            # The last task wanted just a spinner, no bar
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
468
            spin_str =  r'/-\|'[self._spin_pos % 4]
469
            self._spin_pos += 1
470
            return spin_str + ' '
471
        else:
472
            return ''
473
474
    def _format_task(self, task):
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
475
        """Format task-specific parts of progress bar.
476
477
        :returns: (text_part, counter_part) both unicode strings.
478
        """
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
479
        if not task.show_count:
480
            s = ''
4017.1.1 by John Arbash Meinel
Get a pb.tick() to work after calling pb.update()
481
        elif task.current_cnt is not None and task.total_cnt is not None:
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
482
            s = ' %d/%d' % (task.current_cnt, task.total_cnt)
483
        elif task.current_cnt is not None:
484
            s = ' %d' % (task.current_cnt)
485
        else:
486
            s = ''
487
        # compose all the parent messages
488
        t = task
489
        m = task.msg
490
        while t._parent_task:
491
            t = t._parent_task
492
            if t.msg:
493
                m = t.msg + ':' + m
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
494
        return m, s
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
495
4110.2.16 by Martin Pool
Refactor TextProgressView a bit and add another test
496
    def _render_line(self):
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
497
        bar_string = self._render_bar()
498
        if self._last_task:
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
499
            task_part, counter_part = self._format_task(self._last_task)
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
500
        else:
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
501
            task_part = counter_part = ''
4580.3.5 by Martin Pool
selftest sets ProgressTask.show_transport_activity off
502
        if self._last_task and not self._last_task.show_transport_activity:
503
            trans = ''
504
        else:
505
            trans = self._last_transport_msg
5339.2.3 by Martin Pool
Show the progress spinner between the transport rate and the message.
506
        # the bar separates the transport activity from the message, so even
507
        # if there's no bar or spinner, we must show something if both those
508
        # fields are present
509
        if (task_part or trans) and not bar_string:
510
            bar_string = '| '
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
511
        # preferentially truncate the task message if we don't have enough
512
        # space
5339.2.3 by Martin Pool
Show the progress spinner between the transport rate and the message.
513
        avail_width = self._avail_width()
514
        if avail_width is not None:
515
            # if terminal avail_width is unknown, don't truncate
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
516
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
517
            # GZ 2017-04-22: Should measure and truncate task_part properly
5339.2.3 by Martin Pool
Show the progress spinner between the transport rate and the message.
518
            gap = current_len - avail_width
5339.2.1 by Martin Pool
Progress bars should truncate text rather than counters so as not to give a misleading result
519
            if gap > 0:
520
                task_part = task_part[:-gap-2] + '..'
5339.2.3 by Martin Pool
Show the progress spinner between the transport rate and the message.
521
        s = trans + bar_string + task_part + counter_part
522
        if avail_width is not None:
523
            if len(s) < avail_width:
524
                s = s.ljust(avail_width)
525
            elif len(s) > avail_width:
526
                s = s[:avail_width]
527
        return s
4110.2.16 by Martin Pool
Refactor TextProgressView a bit and add another test
528
529
    def _repaint(self):
530
        s = self._render_line()
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
531
        self._show_line(s)
532
        self._have_output = True
533
534
    def show_progress(self, task):
4110.2.15 by Martin Pool
Fix bug in showing task progress and add a test
535
        """Called by the task object when it has changed.
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
536
537
        :param task: The top task object; its parents are also included
4110.2.15 by Martin Pool
Fix bug in showing task progress and add a test
538
            by following links.
539
        """
4110.2.18 by Martin Pool
Progress bars always repaint when task structure is changed
540
        must_update = task is not self._last_task
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
541
        self._last_task = task
542
        now = time.time()
4580.3.1 by Martin Pool
ProgressTasks can specify an update latency
543
        if (not must_update) and (now < self._last_repaint + task.update_latency):
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
544
            return
4110.2.15 by Martin Pool
Fix bug in showing task progress and add a test
545
        if now > self._transport_update_time + 10:
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
546
            # no recent activity; expire it
547
            self._last_transport_msg = ''
548
        self._last_repaint = now
549
        self._repaint()
550
4449.2.1 by Martin Pool
TextUIFactory now respects BZR_PROGRESS_BAR again
551
    def show_transport_activity(self, transport, direction, byte_count):
4110.2.19 by Martin Pool
Transport activity now shows scheme and direction
552
        """Called by transports via the ui_factory, as they do IO.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
553
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
554
        This may update a progress bar, spinner, or similar display.
555
        By default it does nothing.
556
        """
4906.1.8 by John Arbash Meinel
Merge bzr.dev, resolve conflicts.
557
        # XXX: there should be a transport activity model, and that too should
558
        #      be seen by the progress view, rather than being poked in here.
4906.1.2 by John Arbash Meinel
Get the basic interface tested.
559
        self._total_byte_count += byte_count
560
        self._bytes_since_update += byte_count
4906.1.5 by John Arbash Meinel
Include the KiB/s for the transfer.
561
        if self._first_byte_time is None:
562
            # Note that this isn't great, as technically it should be the time
563
            # when the bytes started transferring, not when they completed.
564
            # However, we usually start with a small request anyway.
565
            self._first_byte_time = time.time()
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
566
        if direction in self._bytes_by_direction:
567
            self._bytes_by_direction[direction] += byte_count
568
        else:
569
            self._bytes_by_direction['unknown'] += byte_count
4912.1.4 by Martin Pool
Rename to -Dno_activity; incidentally fixes ReST syntax error
570
        if 'no_activity' in debug.debug_flags:
4912.1.1 by Martin Pool
Transport activity indicator is now shown even if there's no pb
571
            # Can be used as a workaround if
572
            # <https://launchpad.net/bugs/321935> reappears and transport
573
            # activity is cluttering other output.  However, thanks to
574
            # TextUIOutputStream this shouldn't be a problem any more.
4480.1.1 by Martin Pool
(mbp) only show transport activity when progress is already visible
575
            return
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
576
        now = time.time()
4912.1.3 by Martin Pool
Revert: don't show transport activity til some data has been sent.
577
        if self._total_byte_count < 2000:
578
            # a little resistance at first, so it doesn't stay stuck at 0
579
            # while connecting...
580
            return
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
581
        if self._transport_update_time is None:
582
            self._transport_update_time = now
4043.1.1 by John Arbash Meinel
Increase the debounce time for 'transport activity' to 0.5s
583
        elif now >= (self._transport_update_time + 0.5):
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
584
            # guard against clock stepping backwards, and don't update too
585
            # often
4989.1.6 by Vincent Ladeuil
Add comments and update HACKING.txt about which units should be used.
586
            rate = (self._bytes_since_update
587
                    / (now - self._transport_update_time))
588
            # using base-10 units (see HACKING.txt).
5339.2.3 by Martin Pool
Show the progress spinner between the transport rate and the message.
589
            msg = ("%6dkB %5dkB/s " %
4989.1.1 by Gordon Tyler
Changed show_transport_activity and log_transport_activity to use base-10 SI units.
590
                    (self._total_byte_count / 1000, int(rate) / 1000,))
3882.8.9 by Martin Pool
Move TextProgressView to ui.text
591
            self._transport_update_time = now
592
            self._last_repaint = now
593
            self._bytes_since_update = 0
594
            self._last_transport_msg = msg
595
            self._repaint()
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
596
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
597
    def _format_bytes_by_direction(self):
4906.1.5 by John Arbash Meinel
Include the KiB/s for the transfer.
598
        if self._first_byte_time is None:
599
            bps = 0.0
600
        else:
601
            transfer_time = time.time() - self._first_byte_time
602
            if transfer_time < 0.001:
603
                transfer_time = 0.001
604
            bps = self._total_byte_count / transfer_time
605
4989.1.6 by Vincent Ladeuil
Add comments and update HACKING.txt about which units should be used.
606
        # using base-10 units (see HACKING.txt).
4989.1.1 by Gordon Tyler
Changed show_transport_activity and log_transport_activity to use base-10 SI units.
607
        msg = ('Transferred: %.0fkB'
608
               ' (%.1fkB/s r:%.0fkB w:%.0fkB'
609
               % (self._total_byte_count / 1000.,
610
                  bps / 1000.,
611
                  self._bytes_by_direction['read'] / 1000.,
612
                  self._bytes_by_direction['write'] / 1000.,
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
613
                 ))
614
        if self._bytes_by_direction['unknown'] > 0:
4989.1.1 by Gordon Tyler
Changed show_transport_activity and log_transport_activity to use base-10 SI units.
615
            msg += ' u:%.0fkB)' % (
616
                self._bytes_by_direction['unknown'] / 1000.
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
617
                )
618
        else:
619
            msg += ')'
620
        return msg
621
4906.1.1 by John Arbash Meinel
Basic implementation of logging bytes transferred when bzr exits.
622
    def log_transport_activity(self, display=False):
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
623
        msg = self._format_bytes_by_direction()
624
        trace.mutter(msg)
4906.1.7 by John Arbash Meinel
Switch to KiB/K for each value. Don't display if there are no bytes.
625
        if display and self._total_byte_count > 0:
4906.1.3 by John Arbash Meinel
Use clear() so that we clear a progress bar, but don't introduce a newline.
626
            self.clear()
4906.1.4 by John Arbash Meinel
Play around with the ui display a bit more.
627
            self._term_file.write(msg + '\n')
4906.1.1 by John Arbash Meinel
Basic implementation of logging bytes transferred when bzr exits.
628
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
629
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
630
def _get_stream_encoding(stream):
631
    encoding = config.GlobalStack().get('output_encoding')
632
    if encoding is None:
633
        encoding = getattr(stream, "encoding", None)
634
    if encoding is None:
635
        encoding = osutils.get_terminal_encoding(trace=True)
636
    return encoding
637
638
639
def _unwrap_stream(stream):
640
    inner = getattr(stream, "buffer", None)
641
    if inner is None:
642
        inner = getattr(stream, "stream", None)
643
    return inner
644
645
646
def _wrap_in_stream(stream, encoding=None, errors='replace'):
647
    if encoding is None:
648
        encoding = _get_stream_encoding(stream)
649
    encoded_stream = codecs.getreader(encoding)(stream, errors=errors)
650
    encoded_stream.encoding = encoding
651
    return encoded_stream
652
653
654
def _wrap_out_stream(stream, encoding=None, errors='replace'):
655
    if encoding is None:
656
        encoding = _get_stream_encoding(stream)
657
    encoded_stream = codecs.getwriter(encoding)(stream, errors=errors)
658
    encoded_stream.encoding = encoding
659
    return encoded_stream
660
661
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
662
class TextUIOutputStream(object):
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
663
    """Decorates stream to interact better with progress and change encoding.
664
665
    Before writing to the wrapped stream, progress is cleared. Callers must
666
    ensure bulk output is terminated with a newline so progress won't overwrite
667
    partial lines.
668
669
    Additionally, the encoding and errors behaviour of the underlying stream
670
    can be changed at this point. If errors is set to 'exact' raw bytes may be
671
    written to the underlying stream.
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
672
    """
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
673
674
    def __init__(self, ui_factory, stream, encoding=None, errors='strict'):
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
675
        self.ui_factory = ui_factory
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
676
        # GZ 2017-05-21: Clean up semantics when callers are made saner.
677
        inner = _unwrap_stream(stream)
678
        self.raw_stream = None
679
        if errors == "exact":
680
            errors = "strict"
681
            self.raw_stream = inner
682
        if inner is None:
683
            self.wrapped_stream = stream
684
            if encoding is None:
685
                encoding = _get_stream_encoding(stream)
686
        else:
687
            self.wrapped_stream = _wrap_out_stream(inner, encoding, errors)
688
            if encoding is None:
689
                encoding = self.wrapped_stream.encoding
690
        self.encoding = encoding
691
        self.errors = errors
692
693
    def _write(self, to_write):
694
        if isinstance(to_write, bytes):
695
            try:
696
                to_write = to_write.decode(self.encoding, self.errors)
697
            except UnicodeDecodeError:
698
                self.raw_stream.write(to_write)
699
                return
700
        self.wrapped_stream.write(to_write)
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
701
4792.8.7 by Martin Pool
Add TextUIOutputStream.flush
702
    def flush(self):
703
        self.ui_factory.clear_term()
704
        self.wrapped_stream.flush()
705
4792.8.1 by Martin Pool
Add TextUIOutputStream coordinated with progress view
706
    def write(self, to_write):
707
        self.ui_factory.clear_term()
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
708
        self._write(to_write)
4792.8.3 by Martin Pool
Add TextUIOutputStream.writelines
709
710
    def writelines(self, lines):
711
        self.ui_factory.clear_term()
6621.22.1 by Martin
Refactor bzrlib.ui to be based on unicode streams
712
        for line in lines:
713
            self._write(line)