/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

  • Committer: Benoît Pierre
  • Date: 2011-10-08 19:01:59 UTC
  • mto: This revision was merged to the branch mainline in revision 6215.
  • Revision ID: benoit.pierre@gmail.com-20111008190159-k5f8p2eq634r0tcn
Tweak test_text_ui_choose_return_values a little.

Make sure invalid shortcuts are ignored.

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
23
21
import os
24
22
import sys
25
23
import time
26
 
import warnings
27
24
 
28
25
from bzrlib.lazy_import import lazy_import
29
26
lazy_import(globals(), """
 
27
import codecs
 
28
import getpass
 
29
import warnings
 
30
 
30
31
from bzrlib import (
31
32
    debug,
32
33
    progress,
33
34
    osutils,
34
 
    symbol_versioning,
35
35
    trace,
36
36
    )
37
37
 
38
38
""")
39
39
 
40
 
from bzrlib.osutils import watch_sigwinch
41
 
 
42
40
from bzrlib.ui import (
43
41
    UIFactory,
44
42
    NullProgressView,
62
60
        self.stderr = stderr
63
61
        # paints progress, network activity, etc
64
62
        self._progress_view = self.make_progress_view()
65
 
        # hook up the signals to watch for terminal size changes
66
 
        watch_sigwinch()
 
63
 
 
64
    class ChooseUI:
 
65
 
 
66
        """ Helper class for choose implementation.
 
67
        """
 
68
 
 
69
        class InputEOF(StandardError):
 
70
            pass
 
71
 
 
72
        def __init__(self, ui, msg, choices, default):
 
73
            self.ui = ui
 
74
            self._setup_mode()
 
75
            self._build_alternatives(msg, choices, default)
 
76
 
 
77
        def _setup_mode(self):
 
78
            """Setup input mode (line-based, char-based) and echo-back.
 
79
 
 
80
            Line-based input is used if the BZR_TEXTUI_INPUT environment
 
81
            variable is set to 'line-based', or if there is no controlling
 
82
            terminal.
 
83
            """
 
84
            if os.environ.get('BZR_TEXTUI_INPUT') != 'line-based' and \
 
85
               self.ui.stdin == sys.stdin and self.ui.stdin.isatty():
 
86
                self.line_based = False
 
87
                self.echo_back = True
 
88
            else:
 
89
                self.line_based = True
 
90
                self.echo_back = not self.ui.stdin.isatty()
 
91
 
 
92
        def _build_alternatives(self, msg, choices, default):
 
93
            """Parse choices string.
 
94
 
 
95
            Setup final prompt and the lists of choices and associated
 
96
            shortcuts.
 
97
            """
 
98
            index = 0
 
99
            help_list = []
 
100
            self.alternatives = {}
 
101
            choices = choices.split('\n')
 
102
            if default is not None and default not in range(0, len(choices)):
 
103
                raise ValueError("invalid default index")
 
104
            for c in choices:
 
105
                name = c.replace('&', '').lower()
 
106
                choice = (name, index)
 
107
                if name in self.alternatives:
 
108
                    raise ValueError("duplicated choice: %s" % name)
 
109
                self.alternatives[name] = choice
 
110
                shortcut = c.find('&')
 
111
                if -1 != shortcut and (shortcut + 1) < len(c):
 
112
                    help = c[:shortcut]
 
113
                    help += '[' + c[shortcut + 1] + ']'
 
114
                    help += c[(shortcut + 2):]
 
115
                    shortcut = c[shortcut + 1]
 
116
                else:
 
117
                    c = c.replace('&', '')
 
118
                    shortcut = c[0]
 
119
                    help = '[%s]%s' % (shortcut, c[1:])
 
120
                shortcut = shortcut.lower()
 
121
                if shortcut in self.alternatives:
 
122
                    raise ValueError("duplicated shortcut: %s" % shortcut)
 
123
                self.alternatives[shortcut] = choice
 
124
                # Add redirections for default.
 
125
                if index == default:
 
126
                    self.alternatives[''] = choice
 
127
                    self.alternatives['\r'] = choice
 
128
                    self.alternatives['\n'] = choice
 
129
                help_list.append(help)
 
130
                index += 1
 
131
 
 
132
            self.prompt = u'%s (%s): ' % (msg, ', '.join(help_list))
 
133
 
 
134
        def _getline(self):
 
135
            line = self.ui.stdin.readline()
 
136
            if '' == line:
 
137
                raise self.InputEOF
 
138
            return line.strip()
 
139
 
 
140
        def _getchar(self):
 
141
            char = osutils.getchar()
 
142
            if char == chr(3): # INTR
 
143
                raise KeyboardInterrupt
 
144
            if char == chr(4): # EOF (^d, C-d)
 
145
                raise self.InputEOF
 
146
            return char
 
147
 
 
148
        def interact(self):
 
149
            """Keep asking the user until a valid choice is made.
 
150
            """
 
151
            if self.line_based:
 
152
                getchoice = self._getline
 
153
            else:
 
154
                getchoice = self._getchar
 
155
            iter = 0
 
156
            while True:
 
157
                iter += 1
 
158
                if 1 == iter or self.line_based:
 
159
                    self.ui.prompt(self.prompt)
 
160
                try:
 
161
                    choice = getchoice()
 
162
                except self.InputEOF:
 
163
                    self.ui.stderr.write('\n')
 
164
                    return None
 
165
                except KeyboardInterrupt:
 
166
                    self.ui.stderr.write('\n')
 
167
                    raise KeyboardInterrupt
 
168
                choice = choice.lower()
 
169
                if choice not in self.alternatives:
 
170
                    # Not a valid choice, keep on asking.
 
171
                    continue
 
172
                name, index = self.alternatives[choice]
 
173
                if self.echo_back:
 
174
                    self.ui.stderr.write(name + '\n')
 
175
                return index
 
176
 
 
177
    def choose(self, msg, choices, default=None):
 
178
        """Prompt the user for a list of alternatives.
 
179
 
 
180
        Support both line-based and char-based editing.
 
181
 
 
182
        In line-based mode, both the shortcut and full choice name are valid
 
183
        answers, e.g. for choose('prompt', '&yes\n&no'): 'y', ' Y ', ' yes',
 
184
        'YES ' are all valid input lines for choosing 'yes'.
 
185
 
 
186
        An empty line, when in line-based mode, or pressing enter in char-based
 
187
        mode will select the default choice (if any).
 
188
 
 
189
        Choice is echoed back if:
 
190
        - input is char-based; which means a controlling terminal is available,
 
191
          and osutils.getchar is used
 
192
        - input is line-based, and no controlling terminal is available
 
193
        """
 
194
 
 
195
        choose_ui = self.ChooseUI(self, msg, choices, default)
 
196
        return choose_ui.interact()
67
197
 
68
198
    def be_quiet(self, state):
69
199
        if state and not self._quiet:
82
212
        # to clear it.  We might need to separately check for the case of
83
213
        self._progress_view.clear()
84
214
 
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
215
    def get_integer(self, prompt):
98
216
        while True:
99
217
            self.prompt(prompt)
118
236
                password = password[:-1]
119
237
        return password
120
238
 
121
 
    def get_password(self, prompt='', **kwargs):
 
239
    def get_password(self, prompt=u'', **kwargs):
122
240
        """Prompt the user for a password.
123
241
 
124
242
        :param prompt: The prompt to present the user
157
275
        """Construct and return a new ProgressView subclass for this UI.
158
276
        """
159
277
        # with --quiet, never any progress view
160
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/320035>.  Otherwise if the
 
278
        # <https://bugs.launchpad.net/bzr/+bug/320035>.  Otherwise if the
161
279
        # user specifically requests either text or no progress bars, always
162
280
        # do that.  otherwise, guess based on $TERM and tty presence.
163
281
        if self.is_quiet():
202
320
        :param kwargs: Dictionary of arguments to insert into the prompt,
203
321
            to allow UIs to reformat the prompt.
204
322
        """
 
323
        if type(prompt) != unicode:
 
324
            raise ValueError("prompt %r not a unicode string" % prompt)
205
325
        if kwargs:
206
326
            # See <https://launchpad.net/bugs/365891>
207
327
            prompt = prompt % kwargs
208
328
        prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
209
329
        self.clear_term()
 
330
        self.stdout.flush()
210
331
        self.stderr.write(prompt)
211
332
 
212
333
    def report_transport_activity(self, transport, byte_count, direction):
233
354
 
234
355
    def show_warning(self, msg):
235
356
        self.clear_term()
 
357
        if isinstance(msg, unicode):
 
358
            te = osutils.get_terminal_encoding()
 
359
            msg = msg.encode(te, 'replace')
236
360
        self.stderr.write("bzr: warning: %s\n" % msg)
237
361
 
238
362
    def _progress_updated(self, task):
301
425
        # correspond reliably to overall command progress
302
426
        self.enable_bar = False
303
427
 
 
428
    def _avail_width(self):
 
429
        # we need one extra space for terminals that wrap on last char
 
430
        w = osutils.terminal_width() 
 
431
        if w is None:
 
432
            return None
 
433
        else:
 
434
            return w - 1
 
435
 
304
436
    def _show_line(self, s):
305
437
        # sys.stderr.write("progress %r\n" % s)
306
 
        width = osutils.terminal_width()
 
438
        width = self._avail_width()
307
439
        if width is not None:
308
 
            # we need one extra space for terminals that wrap on last char
309
 
            width = width - 1
310
440
            s = '%-*.*s' % (width, width, s)
311
441
        self._term_file.write('\r' + s + '\r')
312
442
 
349
479
            return ''
350
480
 
351
481
    def _format_task(self, task):
 
482
        """Format task-specific parts of progress bar.
 
483
 
 
484
        :returns: (text_part, counter_part) both unicode strings.
 
485
        """
352
486
        if not task.show_count:
353
487
            s = ''
354
488
        elif task.current_cnt is not None and task.total_cnt is not None:
364
498
            t = t._parent_task
365
499
            if t.msg:
366
500
                m = t.msg + ':' + m
367
 
        return m + s
 
501
        return m, s
368
502
 
369
503
    def _render_line(self):
370
504
        bar_string = self._render_bar()
371
505
        if self._last_task:
372
 
            task_msg = self._format_task(self._last_task)
 
506
            task_part, counter_part = self._format_task(self._last_task)
373
507
        else:
374
 
            task_msg = ''
 
508
            task_part = counter_part = ''
375
509
        if self._last_task and not self._last_task.show_transport_activity:
376
510
            trans = ''
377
511
        else:
378
512
            trans = self._last_transport_msg
379
 
            if trans:
380
 
                trans += ' | '
381
 
        return (bar_string + trans + task_msg)
 
513
        # the bar separates the transport activity from the message, so even
 
514
        # if there's no bar or spinner, we must show something if both those
 
515
        # fields are present
 
516
        if (task_part or trans) and not bar_string:
 
517
            bar_string = '| '
 
518
        # preferentially truncate the task message if we don't have enough
 
519
        # space
 
520
        avail_width = self._avail_width()
 
521
        if avail_width is not None:
 
522
            # if terminal avail_width is unknown, don't truncate
 
523
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
 
524
            gap = current_len - avail_width
 
525
            if gap > 0:
 
526
                task_part = task_part[:-gap-2] + '..'
 
527
        s = trans + bar_string + task_part + counter_part
 
528
        if avail_width is not None:
 
529
            if len(s) < avail_width:
 
530
                s = s.ljust(avail_width)
 
531
            elif len(s) > avail_width:
 
532
                s = s[:avail_width]
 
533
        return s
382
534
 
383
535
    def _repaint(self):
384
536
        s = self._render_line()
440
592
            rate = (self._bytes_since_update
441
593
                    / (now - self._transport_update_time))
442
594
            # using base-10 units (see HACKING.txt).
443
 
            msg = ("%6dkB %5dkB/s" %
 
595
            msg = ("%6dkB %5dkB/s " %
444
596
                    (self._total_byte_count / 1000, int(rate) / 1000,))
445
597
            self._transport_update_time = now
446
598
            self._last_repaint = now