~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/text.py

  • Committer: Vincent Ladeuil
  • Date: 2016-02-07 18:23:13 UTC
  • mto: (6615.3.1 2.7)
  • mto: This revision was merged to the branch mainline in revision 6620.
  • Revision ID: v.ladeuil+lp@free.fr-20160207182313-jwz7z3vj4mpyjn7y
Ensure http://pad.lv/1323805 won't come back.

Since the 2.6.0 release pypi policy changed and release tarballs can't be
hosted on launchpad anymore, they have to be uploaded to
http://pypi.python.org/pypi


This fixes setup.py sdist to generate the right tarball with nearly the same
content as the one produced for 2.7.0.

Such a tarball have been uploaded to pypi properly signed and tested for
installation in venv.

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
 
43
46
    )
44
47
 
45
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
 
46
165
class TextUIFactory(UIFactory):
47
 
    """A UI factory for Text user interefaces."""
 
166
    """A UI factory for Text user interfaces."""
48
167
 
49
168
    def __init__(self,
50
169
                 stdin=None,
60
179
        self.stderr = stderr
61
180
        # paints progress, network activity, etc
62
181
        self._progress_view = self.make_progress_view()
63
 
        
 
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()
 
203
 
64
204
    def be_quiet(self, state):
65
205
        if state and not self._quiet:
66
206
            self.clear_term()
78
218
        # to clear it.  We might need to separately check for the case of
79
219
        self._progress_view.clear()
80
220
 
81
 
    def get_boolean(self, prompt):
82
 
        while True:
83
 
            self.prompt(prompt + "? [y/n]: ")
84
 
            line = self.stdin.readline().lower()
85
 
            if line in ('y\n', 'yes\n'):
86
 
                return True
87
 
            elif line in ('n\n', 'no\n'):
88
 
                return False
89
 
            elif line in ('', None):
90
 
                # end-of-file; possibly should raise an error here instead
91
 
                return None
92
 
 
93
221
    def get_integer(self, prompt):
94
222
        while True:
95
223
            self.prompt(prompt)
110
238
            password = self.stdin.readline()
111
239
            if not password:
112
240
                password = None
113
 
            elif password[-1] == '\n':
114
 
                password = password[:-1]
 
241
            else:
 
242
                password = password.decode(self.stdin.encoding)
 
243
 
 
244
                if password[-1] == '\n':
 
245
                    password = password[:-1]
115
246
        return password
116
247
 
117
 
    def get_password(self, prompt='', **kwargs):
 
248
    def get_password(self, prompt=u'', **kwargs):
118
249
        """Prompt the user for a password.
119
250
 
120
251
        :param prompt: The prompt to present the user
145
276
        username = self.stdin.readline()
146
277
        if not username:
147
278
            username = None
148
 
        elif username[-1] == '\n':
149
 
            username = username[:-1]
 
279
        else:
 
280
            username = username.decode(self.stdin.encoding)
 
281
            if username[-1] == '\n':
 
282
                username = username[:-1]
150
283
        return username
151
284
 
152
285
    def make_progress_view(self):
153
286
        """Construct and return a new ProgressView subclass for this UI.
154
287
        """
155
288
        # with --quiet, never any progress view
156
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/320035>.  Otherwise if the
 
289
        # <https://bugs.launchpad.net/bzr/+bug/320035>.  Otherwise if the
157
290
        # user specifically requests either text or no progress bars, always
158
291
        # do that.  otherwise, guess based on $TERM and tty presence.
159
292
        if self.is_quiet():
160
293
            return NullProgressView()
161
 
        elif os.environ.get('BZR_PROGRESS_BAR') == 'text':
162
 
            return TextProgressView(self.stderr)
163
 
        elif os.environ.get('BZR_PROGRESS_BAR') == 'none':
164
 
            return NullProgressView()
165
 
        elif progress._supports_progress(self.stderr):
166
 
            return TextProgressView(self.stderr)
167
 
        else:
168
 
            return NullProgressView()
 
294
        pb_type = config.GlobalStack().get('progress_bar')
 
295
        if pb_type == 'none': # Explicit requirement
 
296
            return NullProgressView()
 
297
        if (pb_type == 'text' # Explicit requirement
 
298
            or progress._supports_progress(self.stderr)): # Guess
 
299
            return TextProgressView(self.stderr)
 
300
        # No explicit requirement and no successful guess
 
301
        return NullProgressView()
169
302
 
170
303
    def _make_output_stream_explicit(self, encoding, encoding_type):
171
304
        if encoding_type == 'exact':
198
331
        :param kwargs: Dictionary of arguments to insert into the prompt,
199
332
            to allow UIs to reformat the prompt.
200
333
        """
 
334
        if type(prompt) != unicode:
 
335
            raise ValueError("prompt %r not a unicode string" % prompt)
201
336
        if kwargs:
202
337
            # See <https://launchpad.net/bugs/365891>
203
338
            prompt = prompt % kwargs
204
 
        prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
 
339
        try:
 
340
            prompt = prompt.encode(self.stderr.encoding)
 
341
        except (UnicodeError, AttributeError):
 
342
            # If stderr has no encoding attribute or can't properly encode,
 
343
            # fallback to terminal encoding for robustness (better display
 
344
            # something to the user than aborting with a traceback).
 
345
            prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
205
346
        self.clear_term()
 
347
        self.stdout.flush()
206
348
        self.stderr.write(prompt)
207
349
 
208
350
    def report_transport_activity(self, transport, byte_count, direction):
229
371
 
230
372
    def show_warning(self, msg):
231
373
        self.clear_term()
 
374
        if isinstance(msg, unicode):
 
375
            te = osutils.get_terminal_encoding()
 
376
            msg = msg.encode(te, 'replace')
232
377
        self.stderr.write("bzr: warning: %s\n" % msg)
233
378
 
234
379
    def _progress_updated(self, task):
249
394
    def _progress_all_finished(self):
250
395
        self._progress_view.clear()
251
396
 
 
397
    def show_user_warning(self, warning_id, **message_args):
 
398
        """Show a text message to the user.
 
399
 
 
400
        Explicitly not for warnings about bzr apis, deprecations or internals.
 
401
        """
 
402
        # eventually trace.warning should migrate here, to avoid logging and
 
403
        # be easier to test; that has a lot of test fallout so for now just
 
404
        # new code can call this
 
405
        if warning_id not in self.suppressed_warnings:
 
406
            self.stderr.write(self.format_user_warning(warning_id, message_args) +
 
407
                '\n')
 
408
 
252
409
 
253
410
class TextProgressView(object):
254
411
    """Display of progress bar and other information on a tty.
265
422
    this only prints the stack from the nominated current task up to the root.
266
423
    """
267
424
 
268
 
    def __init__(self, term_file):
 
425
    def __init__(self, term_file, encoding=None, errors="replace"):
269
426
        self._term_file = term_file
 
427
        if encoding is None:
 
428
            self._encoding = getattr(term_file, "encoding", None) or "ascii"
 
429
        else:
 
430
            self._encoding = encoding
 
431
        self._encoding_errors = errors
270
432
        # true when there's output on the screen we may need to clear
271
433
        self._have_output = False
272
434
        self._last_transport_msg = ''
285
447
        # correspond reliably to overall command progress
286
448
        self.enable_bar = False
287
449
 
288
 
    def _show_line(self, s):
289
 
        # sys.stderr.write("progress %r\n" % s)
290
 
        width = osutils.terminal_width()
 
450
    def _avail_width(self):
 
451
        # we need one extra space for terminals that wrap on last char
 
452
        w = osutils.terminal_width() 
 
453
        if w is None:
 
454
            return None
 
455
        else:
 
456
            return w - 1
 
457
 
 
458
    def _show_line(self, u):
 
459
        s = u.encode(self._encoding, self._encoding_errors)
 
460
        width = self._avail_width()
291
461
        if width is not None:
292
 
            # we need one extra space for terminals that wrap on last char
293
 
            width = width - 1
 
462
            # GZ 2012-03-28: Counting bytes is wrong for calculating width of
 
463
            #                text but better than counting codepoints.
294
464
            s = '%-*.*s' % (width, width, s)
295
465
        self._term_file.write('\r' + s + '\r')
296
466
 
333
503
            return ''
334
504
 
335
505
    def _format_task(self, task):
 
506
        """Format task-specific parts of progress bar.
 
507
 
 
508
        :returns: (text_part, counter_part) both unicode strings.
 
509
        """
336
510
        if not task.show_count:
337
511
            s = ''
338
512
        elif task.current_cnt is not None and task.total_cnt is not None:
348
522
            t = t._parent_task
349
523
            if t.msg:
350
524
                m = t.msg + ':' + m
351
 
        return m + s
 
525
        return m, s
352
526
 
353
527
    def _render_line(self):
354
528
        bar_string = self._render_bar()
355
529
        if self._last_task:
356
 
            task_msg = self._format_task(self._last_task)
 
530
            task_part, counter_part = self._format_task(self._last_task)
357
531
        else:
358
 
            task_msg = ''
 
532
            task_part = counter_part = ''
359
533
        if self._last_task and not self._last_task.show_transport_activity:
360
534
            trans = ''
361
535
        else:
362
536
            trans = self._last_transport_msg
363
 
            if trans:
364
 
                trans += ' | '
365
 
        return (bar_string + trans + task_msg)
 
537
        # the bar separates the transport activity from the message, so even
 
538
        # if there's no bar or spinner, we must show something if both those
 
539
        # fields are present
 
540
        if (task_part or trans) and not bar_string:
 
541
            bar_string = '| '
 
542
        # preferentially truncate the task message if we don't have enough
 
543
        # space
 
544
        avail_width = self._avail_width()
 
545
        if avail_width is not None:
 
546
            # if terminal avail_width is unknown, don't truncate
 
547
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
 
548
            gap = current_len - avail_width
 
549
            if gap > 0:
 
550
                task_part = task_part[:-gap-2] + '..'
 
551
        s = trans + bar_string + task_part + counter_part
 
552
        if avail_width is not None:
 
553
            if len(s) < avail_width:
 
554
                s = s.ljust(avail_width)
 
555
            elif len(s) > avail_width:
 
556
                s = s[:avail_width]
 
557
        return s
366
558
 
367
559
    def _repaint(self):
368
560
        s = self._render_line()
424
616
            rate = (self._bytes_since_update
425
617
                    / (now - self._transport_update_time))
426
618
            # using base-10 units (see HACKING.txt).
427
 
            msg = ("%6dkB %5dkB/s" %
 
619
            msg = ("%6dkB %5dkB/s " %
428
620
                    (self._total_byte_count / 1000, int(rate) / 1000,))
429
621
            self._transport_update_time = now
430
622
            self._last_repaint = now