/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Text UI, write output to the console."""
 
17
 
 
18
"""Text UI, write output to the console.
 
19
"""
18
20
 
19
21
from __future__ import absolute_import
20
22
 
21
 
import codecs
22
23
import os
23
24
import sys
24
 
import warnings
 
25
import time
25
26
 
26
 
from ..lazy_import import lazy_import
 
27
from bzrlib.lazy_import import lazy_import
27
28
lazy_import(globals(), """
 
29
import codecs
28
30
import getpass
29
 
import time
 
31
import warnings
30
32
 
31
 
from breezy import (
 
33
from bzrlib import (
 
34
    config,
32
35
    debug,
33
36
    progress,
34
 
    )
35
 
""")
36
 
 
37
 
from .. import (
38
 
    config,
39
37
    osutils,
40
38
    trace,
41
39
    )
42
 
from ..sixish import (
43
 
    text_type,
44
 
    )
45
 
from . import (
 
40
 
 
41
""")
 
42
 
 
43
from bzrlib.ui import (
 
44
    UIFactory,
46
45
    NullProgressView,
47
 
    UIFactory,
48
46
    )
49
47
 
50
48
 
61
59
    def _setup_mode(self):
62
60
        """Setup input mode (line-based, char-based) and echo-back.
63
61
 
64
 
        Line-based input is used if the BRZ_TEXTUI_INPUT environment
 
62
        Line-based input is used if the BZR_TEXTUI_INPUT environment
65
63
        variable is set to 'line-based', or if there is no controlling
66
64
        terminal.
67
65
        """
68
 
        is_tty = self.ui.raw_stdin.isatty()
69
 
        if (os.environ.get('BRZ_TEXTUI_INPUT') != 'line-based' and
70
 
                self.ui.raw_stdin == sys.stdin and is_tty):
 
66
        if os.environ.get('BZR_TEXTUI_INPUT') != 'line-based' and \
 
67
           self.ui.stdin == sys.stdin and self.ui.stdin.isatty():
71
68
            self.line_based = False
72
69
            self.echo_back = True
73
70
        else:
74
71
            self.line_based = True
75
 
            self.echo_back = not is_tty
 
72
            self.echo_back = not self.ui.stdin.isatty()
76
73
 
77
74
    def _build_alternatives(self, msg, choices, default):
78
75
        """Parse choices string.
127
124
            raise KeyboardInterrupt
128
125
        if char == chr(4): # EOF (^d, C-d)
129
126
            raise EOFError
130
 
        if isinstance(char, bytes):
131
 
            return char.decode('ascii', 'replace')
132
127
        return char
133
128
 
134
129
    def interact(self):
146
141
            try:
147
142
                choice = getchoice()
148
143
            except EOFError:
149
 
                self.ui.stderr.write(u'\n')
 
144
                self.ui.stderr.write('\n')
150
145
                return None
151
146
            except KeyboardInterrupt:
152
 
                self.ui.stderr.write(u'\n')
153
 
                raise
 
147
                self.ui.stderr.write('\n')
 
148
                raise KeyboardInterrupt
154
149
            choice = choice.lower()
155
150
            if choice not in self.alternatives:
156
151
                # Not a valid choice, keep on asking.
157
152
                continue
158
153
            name, index = self.alternatives[choice]
159
154
            if self.echo_back:
160
 
                self.ui.stderr.write(name + u'\n')
 
155
                self.ui.stderr.write(name + '\n')
161
156
            return index
162
157
 
163
158
 
164
159
opt_progress_bar = config.Option(
165
160
    'progress_bar', help='Progress bar type.',
166
 
    default_from_env=['BRZ_PROGRESS_BAR'], default=None,
 
161
    default_from_env=['BZR_PROGRESS_BAR'], default=None,
167
162
    invalid='error')
168
163
 
169
164
 
170
165
class TextUIFactory(UIFactory):
171
166
    """A UI factory for Text user interfaces."""
172
167
 
173
 
    def __init__(self, stdin, stdout, stderr):
174
 
        """Create a TextUIFactory."""
 
168
    def __init__(self,
 
169
                 stdin=None,
 
170
                 stdout=None,
 
171
                 stderr=None):
 
172
        """Create a TextUIFactory.
 
173
        """
175
174
        super(TextUIFactory, self).__init__()
 
175
        # TODO: there's no good reason not to pass all three streams, maybe we
 
176
        # should deprecate the default values...
176
177
        self.stdin = stdin
177
178
        self.stdout = stdout
178
179
        self.stderr = stderr
179
 
        self._progress_view = NullProgressView()
180
 
 
181
 
    def __enter__(self):
182
 
        # Choose default encoding and handle py2/3 differences
183
 
        self._setup_streams()
184
180
        # paints progress, network activity, etc
185
181
        self._progress_view = self.make_progress_view()
186
 
        return self
187
 
 
188
 
    def _setup_streams(self):
189
 
        self.raw_stdin = _unwrap_stream(self.stdin)
190
 
        self.stdin = _wrap_in_stream(self.raw_stdin)
191
 
        self.raw_stdout = _unwrap_stream(self.stdout)
192
 
        self.stdout = _wrap_out_stream(self.raw_stdout)
193
 
        self.raw_stderr = _unwrap_stream(self.stderr)
194
 
        self.stderr = _wrap_out_stream(self.raw_stderr)
195
182
 
196
183
    def choose(self, msg, choices, default=None):
197
184
        """Prompt the user for a list of alternatives.
252
239
            if not password:
253
240
                password = None
254
241
            else:
 
242
                password = password.decode(self.stdin.encoding)
 
243
 
255
244
                if password[-1] == '\n':
256
245
                    password = password[:-1]
257
246
        return password
288
277
        if not username:
289
278
            username = None
290
279
        else:
 
280
            username = username.decode(self.stdin.encoding)
291
281
            if username[-1] == '\n':
292
282
                username = username[:-1]
293
283
        return username
311
301
        return NullProgressView()
312
302
 
313
303
    def _make_output_stream_explicit(self, encoding, encoding_type):
314
 
        return TextUIOutputStream(self, self.stdout, encoding, encoding_type)
 
304
        if encoding_type == 'exact':
 
305
            # force sys.stdout to be binary stream on win32; 
 
306
            # NB: this leaves the file set in that mode; may cause problems if
 
307
            # one process tries to do binary and then text output
 
308
            if sys.platform == 'win32':
 
309
                fileno = getattr(self.stdout, 'fileno', None)
 
310
                if fileno:
 
311
                    import msvcrt
 
312
                    msvcrt.setmode(fileno(), os.O_BINARY)
 
313
            return TextUIOutputStream(self, self.stdout)
 
314
        else:
 
315
            encoded_stdout = codecs.getwriter(encoding)(self.stdout,
 
316
                errors=encoding_type)
 
317
            # For whatever reason codecs.getwriter() does not advertise its encoding
 
318
            # it just returns the encoding of the wrapped file, which is completely
 
319
            # bogus. So set the attribute, so we can find the correct encoding later.
 
320
            encoded_stdout.encoding = encoding
 
321
            return TextUIOutputStream(self, encoded_stdout)
315
322
 
316
323
    def note(self, msg):
317
324
        """Write an already-formatted message, clearing the progress bar if necessary."""
320
327
 
321
328
    def prompt(self, prompt, **kwargs):
322
329
        """Emit prompt on the CLI.
323
 
 
 
330
        
324
331
        :param kwargs: Dictionary of arguments to insert into the prompt,
325
332
            to allow UIs to reformat the prompt.
326
333
        """
327
 
        if not isinstance(prompt, text_type):
 
334
        if type(prompt) != unicode:
328
335
            raise ValueError("prompt %r not a unicode string" % prompt)
329
336
        if kwargs:
330
337
            # See <https://launchpad.net/bugs/365891>
331
338
            prompt = prompt % kwargs
 
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')
332
346
        self.clear_term()
333
347
        self.stdout.flush()
334
348
        self.stderr.write(prompt)
357
371
 
358
372
    def show_warning(self, msg):
359
373
        self.clear_term()
 
374
        if isinstance(msg, unicode):
 
375
            te = osutils.get_terminal_encoding()
 
376
            msg = msg.encode(te, 'replace')
360
377
        self.stderr.write("bzr: warning: %s\n" % msg)
361
378
 
362
379
    def _progress_updated(self, task):
386
403
        # be easier to test; that has a lot of test fallout so for now just
387
404
        # new code can call this
388
405
        if warning_id not in self.suppressed_warnings:
389
 
            warning = self.format_user_warning(warning_id, message_args)
390
 
            self.stderr.write(warning + '\n')
391
 
 
392
 
 
393
 
def pad_to_width(line, width, encoding_hint='ascii'):
394
 
    """Truncate or pad unicode line to width.
395
 
 
396
 
    This is best-effort for now, and strings containing control codes or
397
 
    non-ascii text may be cut and padded incorrectly.
398
 
    """
399
 
    s = line.encode(encoding_hint, 'replace')
400
 
    return (b'%-*.*s' % (width, width, s)).decode(encoding_hint)
 
406
            self.stderr.write(self.format_user_warning(warning_id, message_args) +
 
407
                '\n')
401
408
 
402
409
 
403
410
class TextProgressView(object):
404
411
    """Display of progress bar and other information on a tty.
405
412
 
406
 
    This shows one line of text, including possibly a network indicator,
407
 
    spinner, progress bar, message, etc.
 
413
    This shows one line of text, including possibly a network indicator, spinner,
 
414
    progress bar, message, etc.
408
415
 
409
416
    One instance of this is created and held by the UI, and fed updates when a
410
417
    task wants to be painted.
415
422
    this only prints the stack from the nominated current task up to the root.
416
423
    """
417
424
 
418
 
    def __init__(self, term_file, encoding=None, errors=None):
 
425
    def __init__(self, term_file, encoding=None, errors="replace"):
419
426
        self._term_file = term_file
420
427
        if encoding is None:
421
428
            self._encoding = getattr(term_file, "encoding", None) or "ascii"
422
429
        else:
423
430
            self._encoding = encoding
 
431
        self._encoding_errors = errors
424
432
        # true when there's output on the screen we may need to clear
425
433
        self._have_output = False
426
434
        self._last_transport_msg = ''
435
443
        self._bytes_by_direction = {'unknown': 0, 'read': 0, 'write': 0}
436
444
        self._first_byte_time = None
437
445
        self._fraction = 0
438
 
        # force the progress bar to be off, as at the moment it doesn't
 
446
        # force the progress bar to be off, as at the moment it doesn't 
439
447
        # correspond reliably to overall command progress
440
448
        self.enable_bar = False
441
449
 
442
450
    def _avail_width(self):
443
451
        # we need one extra space for terminals that wrap on last char
444
 
        w = osutils.terminal_width()
 
452
        w = osutils.terminal_width() 
445
453
        if w is None:
446
454
            return None
447
455
        else:
448
456
            return w - 1
449
457
 
450
458
    def _show_line(self, u):
 
459
        s = u.encode(self._encoding, self._encoding_errors)
451
460
        width = self._avail_width()
452
461
        if width is not None:
453
 
            u = pad_to_width(u, width, encoding_hint=self._encoding)
454
 
        self._term_file.write('\r' + u + '\r')
 
462
            # GZ 2012-03-28: Counting bytes is wrong for calculating width of
 
463
            #                text but better than counting codepoints.
 
464
            s = '%-*.*s' % (width, width, s)
 
465
        self._term_file.write('\r' + s + '\r')
455
466
 
456
467
    def clear(self):
457
468
        if self._have_output:
478
489
                    self._last_task._overall_completion_fraction() or 0
479
490
            if (completion_fraction < self._fraction and 'progress' in
480
491
                debug.debug_flags):
481
 
                debug.set_trace()
 
492
                import pdb;pdb.set_trace()
482
493
            self._fraction = completion_fraction
483
494
            markers = int(round(float(cols) * completion_fraction)) - 1
484
495
            bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
534
545
        if avail_width is not None:
535
546
            # if terminal avail_width is unknown, don't truncate
536
547
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
537
 
            # GZ 2017-04-22: Should measure and truncate task_part properly
538
548
            gap = current_len - avail_width
539
549
            if gap > 0:
540
550
                task_part = task_part[:-gap-2] + '..'
553
563
 
554
564
    def show_progress(self, task):
555
565
        """Called by the task object when it has changed.
556
 
 
557
 
        :param task: The top task object; its parents are also included
 
566
        
 
567
        :param task: The top task object; its parents are also included 
558
568
            by following links.
559
569
        """
560
570
        must_update = task is not self._last_task
647
657
            self._term_file.write(msg + '\n')
648
658
 
649
659
 
650
 
def _get_stream_encoding(stream):
651
 
    encoding = config.GlobalStack().get('output_encoding')
652
 
    if encoding is None:
653
 
        encoding = getattr(stream, "encoding", None)
654
 
    if encoding is None:
655
 
        encoding = osutils.get_terminal_encoding(trace=True)
656
 
    return encoding
657
 
 
658
 
 
659
 
def _unwrap_stream(stream):
660
 
    inner = getattr(stream, "buffer", None)
661
 
    if inner is None:
662
 
        inner = getattr(stream, "stream", stream)
663
 
    return inner
664
 
 
665
 
 
666
 
def _wrap_in_stream(stream, encoding=None, errors='replace'):
667
 
    if encoding is None:
668
 
        encoding = _get_stream_encoding(stream)
669
 
    encoded_stream = codecs.getreader(encoding)(stream, errors=errors)
670
 
    encoded_stream.encoding = encoding
671
 
    return encoded_stream
672
 
 
673
 
 
674
 
def _wrap_out_stream(stream, encoding=None, errors='replace'):
675
 
    if encoding is None:
676
 
        encoding = _get_stream_encoding(stream)
677
 
    encoded_stream = codecs.getwriter(encoding)(stream, errors=errors)
678
 
    encoded_stream.encoding = encoding
679
 
    return encoded_stream
680
 
 
681
 
 
682
660
class TextUIOutputStream(object):
683
 
    """Decorates stream to interact better with progress and change encoding.
684
 
 
685
 
    Before writing to the wrapped stream, progress is cleared. Callers must
686
 
    ensure bulk output is terminated with a newline so progress won't overwrite
687
 
    partial lines.
688
 
 
689
 
    Additionally, the encoding and errors behaviour of the underlying stream
690
 
    can be changed at this point. If errors is set to 'exact' raw bytes may be
691
 
    written to the underlying stream.
 
661
    """Decorates an output stream so that the terminal is cleared before writing.
 
662
 
 
663
    This is supposed to ensure that the progress bar does not conflict with bulk
 
664
    text output.
692
665
    """
693
 
 
694
 
    def __init__(self, ui_factory, stream, encoding=None, errors='strict'):
 
666
    # XXX: this does not handle the case of writing part of a line, then doing
 
667
    # progress bar output: the progress bar will probably write over it.
 
668
    # one option is just to buffer that text until we have a full line;
 
669
    # another is to save and restore it
 
670
 
 
671
    # XXX: might need to wrap more methods
 
672
 
 
673
    def __init__(self, ui_factory, wrapped_stream):
695
674
        self.ui_factory = ui_factory
696
 
        # GZ 2017-05-21: Clean up semantics when callers are made saner.
697
 
        inner = _unwrap_stream(stream)
698
 
        self.raw_stream = None
699
 
        if errors == "exact":
700
 
            errors = "strict"
701
 
            self.raw_stream = inner
702
 
        if inner is None:
703
 
            self.wrapped_stream = stream
704
 
            if encoding is None:
705
 
                encoding = _get_stream_encoding(stream)
706
 
        else:
707
 
            self.wrapped_stream = _wrap_out_stream(inner, encoding, errors)
708
 
            if encoding is None:
709
 
                encoding = self.wrapped_stream.encoding
710
 
        self.encoding = encoding
711
 
        self.errors = errors
712
 
 
713
 
    def _write(self, to_write):
714
 
        if isinstance(to_write, bytes):
715
 
            try:
716
 
                to_write = to_write.decode(self.encoding, self.errors)
717
 
            except UnicodeDecodeError:
718
 
                self.raw_stream.write(to_write)
719
 
                return
720
 
        self.wrapped_stream.write(to_write)
 
675
        self.wrapped_stream = wrapped_stream
 
676
        # this does no transcoding, but it must expose the underlying encoding
 
677
        # because some callers need to know what can be written - see for
 
678
        # example unescape_for_display.
 
679
        self.encoding = getattr(wrapped_stream, 'encoding', None)
721
680
 
722
681
    def flush(self):
723
682
        self.ui_factory.clear_term()
725
684
 
726
685
    def write(self, to_write):
727
686
        self.ui_factory.clear_term()
728
 
        self._write(to_write)
 
687
        self.wrapped_stream.write(to_write)
729
688
 
730
689
    def writelines(self, lines):
731
690
        self.ui_factory.clear_term()
732
 
        for line in lines:
733
 
            self._write(line)
 
691
        self.wrapped_stream.writelines(lines)