/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

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