/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/ui/text.py

(vila) Add a config option for the progress bar type. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
from __future__ import absolute_import
 
22
 
23
23
import os
24
24
import sys
25
25
import time
26
 
import warnings
27
26
 
28
27
from bzrlib.lazy_import import lazy_import
29
28
lazy_import(globals(), """
 
29
import codecs
 
30
import getpass
 
31
import warnings
 
32
 
30
33
from bzrlib import (
 
34
    config,
31
35
    debug,
32
36
    progress,
33
37
    osutils,
34
 
    symbol_versioning,
35
38
    trace,
36
39
    )
37
40
 
38
41
""")
39
42
 
40
 
from bzrlib.osutils import watch_sigwinch
41
 
 
42
43
from bzrlib.ui import (
43
44
    UIFactory,
44
45
    NullProgressView,
45
46
    )
46
47
 
47
48
 
 
49
class _ChooseUI(object):
 
50
 
 
51
    """ Helper class for choose implementation.
 
52
    """
 
53
 
 
54
    def __init__(self, ui, msg, choices, default):
 
55
        self.ui = ui
 
56
        self._setup_mode()
 
57
        self._build_alternatives(msg, choices, default)
 
58
 
 
59
    def _setup_mode(self):
 
60
        """Setup input mode (line-based, char-based) and echo-back.
 
61
 
 
62
        Line-based input is used if the BZR_TEXTUI_INPUT environment
 
63
        variable is set to 'line-based', or if there is no controlling
 
64
        terminal.
 
65
        """
 
66
        if os.environ.get('BZR_TEXTUI_INPUT') != 'line-based' and \
 
67
           self.ui.stdin == sys.stdin and self.ui.stdin.isatty():
 
68
            self.line_based = False
 
69
            self.echo_back = True
 
70
        else:
 
71
            self.line_based = True
 
72
            self.echo_back = not self.ui.stdin.isatty()
 
73
 
 
74
    def _build_alternatives(self, msg, choices, default):
 
75
        """Parse choices string.
 
76
 
 
77
        Setup final prompt and the lists of choices and associated
 
78
        shortcuts.
 
79
        """
 
80
        index = 0
 
81
        help_list = []
 
82
        self.alternatives = {}
 
83
        choices = choices.split('\n')
 
84
        if default is not None and default not in range(0, len(choices)):
 
85
            raise ValueError("invalid default index")
 
86
        for c in choices:
 
87
            name = c.replace('&', '').lower()
 
88
            choice = (name, index)
 
89
            if name in self.alternatives:
 
90
                raise ValueError("duplicated choice: %s" % name)
 
91
            self.alternatives[name] = choice
 
92
            shortcut = c.find('&')
 
93
            if -1 != shortcut and (shortcut + 1) < len(c):
 
94
                help = c[:shortcut]
 
95
                help += '[' + c[shortcut + 1] + ']'
 
96
                help += c[(shortcut + 2):]
 
97
                shortcut = c[shortcut + 1]
 
98
            else:
 
99
                c = c.replace('&', '')
 
100
                shortcut = c[0]
 
101
                help = '[%s]%s' % (shortcut, c[1:])
 
102
            shortcut = shortcut.lower()
 
103
            if shortcut in self.alternatives:
 
104
                raise ValueError("duplicated shortcut: %s" % shortcut)
 
105
            self.alternatives[shortcut] = choice
 
106
            # Add redirections for default.
 
107
            if index == default:
 
108
                self.alternatives[''] = choice
 
109
                self.alternatives['\r'] = choice
 
110
            help_list.append(help)
 
111
            index += 1
 
112
 
 
113
        self.prompt = u'%s (%s): ' % (msg, ', '.join(help_list))
 
114
 
 
115
    def _getline(self):
 
116
        line = self.ui.stdin.readline()
 
117
        if '' == line:
 
118
            raise EOFError
 
119
        return line.strip()
 
120
 
 
121
    def _getchar(self):
 
122
        char = osutils.getchar()
 
123
        if char == chr(3): # INTR
 
124
            raise KeyboardInterrupt
 
125
        if char == chr(4): # EOF (^d, C-d)
 
126
            raise EOFError
 
127
        return char
 
128
 
 
129
    def interact(self):
 
130
        """Keep asking the user until a valid choice is made.
 
131
        """
 
132
        if self.line_based:
 
133
            getchoice = self._getline
 
134
        else:
 
135
            getchoice = self._getchar
 
136
        iter = 0
 
137
        while True:
 
138
            iter += 1
 
139
            if 1 == iter or self.line_based:
 
140
                self.ui.prompt(self.prompt)
 
141
            try:
 
142
                choice = getchoice()
 
143
            except EOFError:
 
144
                self.ui.stderr.write('\n')
 
145
                return None
 
146
            except KeyboardInterrupt:
 
147
                self.ui.stderr.write('\n')
 
148
                raise KeyboardInterrupt
 
149
            choice = choice.lower()
 
150
            if choice not in self.alternatives:
 
151
                # Not a valid choice, keep on asking.
 
152
                continue
 
153
            name, index = self.alternatives[choice]
 
154
            if self.echo_back:
 
155
                self.ui.stderr.write(name + '\n')
 
156
            return index
 
157
 
 
158
 
 
159
opt_progress_bar = config.Option(
 
160
    'progress_bar', help='Progress bar type.',
 
161
    default_from_env=['BZR_PROGRESS_BAR'], default=None,
 
162
    invalid='error')
 
163
 
 
164
 
48
165
class TextUIFactory(UIFactory):
49
 
    """A UI factory for Text user interefaces."""
 
166
    """A UI factory for Text user interfaces."""
50
167
 
51
168
    def __init__(self,
52
169
                 stdin=None,
62
179
        self.stderr = stderr
63
180
        # paints progress, network activity, etc
64
181
        self._progress_view = self.make_progress_view()
65
 
        # hook up the signals to watch for terminal size changes
66
 
        watch_sigwinch()
 
182
 
 
183
    def choose(self, msg, choices, default=None):
 
184
        """Prompt the user for a list of alternatives.
 
185
 
 
186
        Support both line-based and char-based editing.
 
187
 
 
188
        In line-based mode, both the shortcut and full choice name are valid
 
189
        answers, e.g. for choose('prompt', '&yes\n&no'): 'y', ' Y ', ' yes',
 
190
        'YES ' are all valid input lines for choosing 'yes'.
 
191
 
 
192
        An empty line, when in line-based mode, or pressing enter in char-based
 
193
        mode will select the default choice (if any).
 
194
 
 
195
        Choice is echoed back if:
 
196
        - input is char-based; which means a controlling terminal is available,
 
197
          and osutils.getchar is used
 
198
        - input is line-based, and no controlling terminal is available
 
199
        """
 
200
 
 
201
        choose_ui = _ChooseUI(self, msg, choices, default)
 
202
        return choose_ui.interact()
67
203
 
68
204
    def be_quiet(self, state):
69
205
        if state and not self._quiet:
82
218
        # to clear it.  We might need to separately check for the case of
83
219
        self._progress_view.clear()
84
220
 
85
 
    def get_boolean(self, prompt):
86
 
        while True:
87
 
            self.prompt(prompt + "? [y/n]: ")
88
 
            line = self.stdin.readline().lower()
89
 
            if line in ('y\n', 'yes\n'):
90
 
                return True
91
 
            elif line in ('n\n', 'no\n'):
92
 
                return False
93
 
            elif line in ('', None):
94
 
                # end-of-file; possibly should raise an error here instead
95
 
                return None
96
 
 
97
221
    def get_integer(self, prompt):
98
222
        while True:
99
223
            self.prompt(prompt)
118
242
                password = password[:-1]
119
243
        return password
120
244
 
121
 
    def get_password(self, prompt='', **kwargs):
 
245
    def get_password(self, prompt=u'', **kwargs):
122
246
        """Prompt the user for a password.
123
247
 
124
248
        :param prompt: The prompt to present the user
157
281
        """Construct and return a new ProgressView subclass for this UI.
158
282
        """
159
283
        # with --quiet, never any progress view
160
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/320035>.  Otherwise if the
 
284
        # <https://bugs.launchpad.net/bzr/+bug/320035>.  Otherwise if the
161
285
        # user specifically requests either text or no progress bars, always
162
286
        # do that.  otherwise, guess based on $TERM and tty presence.
163
287
        if self.is_quiet():
164
288
            return NullProgressView()
165
 
        elif os.environ.get('BZR_PROGRESS_BAR') == 'text':
166
 
            return TextProgressView(self.stderr)
167
 
        elif os.environ.get('BZR_PROGRESS_BAR') == 'none':
168
 
            return NullProgressView()
169
 
        elif progress._supports_progress(self.stderr):
170
 
            return TextProgressView(self.stderr)
171
 
        else:
172
 
            return NullProgressView()
 
289
        pb_type = config.GlobalStack().get('progress_bar')
 
290
        if pb_type == 'none': # Explicit requirement
 
291
            return NullProgressView()
 
292
        if (pb_type == 'text' # Explicit requirement
 
293
            or progress._supports_progress(self.stderr)): # Guess
 
294
            return TextProgressView(self.stderr)
 
295
        # No explicit requirement and no successful guess
 
296
        return NullProgressView()
173
297
 
174
298
    def _make_output_stream_explicit(self, encoding, encoding_type):
175
299
        if encoding_type == 'exact':
202
326
        :param kwargs: Dictionary of arguments to insert into the prompt,
203
327
            to allow UIs to reformat the prompt.
204
328
        """
 
329
        if type(prompt) != unicode:
 
330
            raise ValueError("prompt %r not a unicode string" % prompt)
205
331
        if kwargs:
206
332
            # See <https://launchpad.net/bugs/365891>
207
333
            prompt = prompt % kwargs
208
334
        prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
209
335
        self.clear_term()
 
336
        self.stdout.flush()
210
337
        self.stderr.write(prompt)
211
338
 
212
339
    def report_transport_activity(self, transport, byte_count, direction):
233
360
 
234
361
    def show_warning(self, msg):
235
362
        self.clear_term()
 
363
        if isinstance(msg, unicode):
 
364
            te = osutils.get_terminal_encoding()
 
365
            msg = msg.encode(te, 'replace')
236
366
        self.stderr.write("bzr: warning: %s\n" % msg)
237
367
 
238
368
    def _progress_updated(self, task):
281
411
    this only prints the stack from the nominated current task up to the root.
282
412
    """
283
413
 
284
 
    def __init__(self, term_file):
 
414
    def __init__(self, term_file, encoding=None, errors="replace"):
285
415
        self._term_file = term_file
 
416
        if encoding is None:
 
417
            self._encoding = getattr(term_file, "encoding", None) or "ascii"
 
418
        else:
 
419
            self._encoding = encoding
 
420
        self._encoding_errors = errors
286
421
        # true when there's output on the screen we may need to clear
287
422
        self._have_output = False
288
423
        self._last_transport_msg = ''
301
436
        # correspond reliably to overall command progress
302
437
        self.enable_bar = False
303
438
 
304
 
    def _show_line(self, s):
305
 
        # sys.stderr.write("progress %r\n" % s)
306
 
        width = osutils.terminal_width()
 
439
    def _avail_width(self):
 
440
        # we need one extra space for terminals that wrap on last char
 
441
        w = osutils.terminal_width() 
 
442
        if w is None:
 
443
            return None
 
444
        else:
 
445
            return w - 1
 
446
 
 
447
    def _show_line(self, u):
 
448
        s = u.encode(self._encoding, self._encoding_errors)
 
449
        width = self._avail_width()
307
450
        if width is not None:
308
 
            # we need one extra space for terminals that wrap on last char
309
 
            width = width - 1
 
451
            # GZ 2012-03-28: Counting bytes is wrong for calculating width of
 
452
            #                text but better than counting codepoints.
310
453
            s = '%-*.*s' % (width, width, s)
311
454
        self._term_file.write('\r' + s + '\r')
312
455
 
349
492
            return ''
350
493
 
351
494
    def _format_task(self, task):
 
495
        """Format task-specific parts of progress bar.
 
496
 
 
497
        :returns: (text_part, counter_part) both unicode strings.
 
498
        """
352
499
        if not task.show_count:
353
500
            s = ''
354
501
        elif task.current_cnt is not None and task.total_cnt is not None:
364
511
            t = t._parent_task
365
512
            if t.msg:
366
513
                m = t.msg + ':' + m
367
 
        return m + s
 
514
        return m, s
368
515
 
369
516
    def _render_line(self):
370
517
        bar_string = self._render_bar()
371
518
        if self._last_task:
372
 
            task_msg = self._format_task(self._last_task)
 
519
            task_part, counter_part = self._format_task(self._last_task)
373
520
        else:
374
 
            task_msg = ''
 
521
            task_part = counter_part = ''
375
522
        if self._last_task and not self._last_task.show_transport_activity:
376
523
            trans = ''
377
524
        else:
378
525
            trans = self._last_transport_msg
379
 
            if trans:
380
 
                trans += ' | '
381
 
        return (bar_string + trans + task_msg)
 
526
        # the bar separates the transport activity from the message, so even
 
527
        # if there's no bar or spinner, we must show something if both those
 
528
        # fields are present
 
529
        if (task_part or trans) and not bar_string:
 
530
            bar_string = '| '
 
531
        # preferentially truncate the task message if we don't have enough
 
532
        # space
 
533
        avail_width = self._avail_width()
 
534
        if avail_width is not None:
 
535
            # if terminal avail_width is unknown, don't truncate
 
536
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
 
537
            gap = current_len - avail_width
 
538
            if gap > 0:
 
539
                task_part = task_part[:-gap-2] + '..'
 
540
        s = trans + bar_string + task_part + counter_part
 
541
        if avail_width is not None:
 
542
            if len(s) < avail_width:
 
543
                s = s.ljust(avail_width)
 
544
            elif len(s) > avail_width:
 
545
                s = s[:avail_width]
 
546
        return s
382
547
 
383
548
    def _repaint(self):
384
549
        s = self._render_line()
440
605
            rate = (self._bytes_since_update
441
606
                    / (now - self._transport_update_time))
442
607
            # using base-10 units (see HACKING.txt).
443
 
            msg = ("%6dkB %5dkB/s" %
 
608
            msg = ("%6dkB %5dkB/s " %
444
609
                    (self._total_byte_count / 1000, int(rate) / 1000,))
445
610
            self._transport_update_time = now
446
611
            self._last_repaint = now