/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 breezy/ui/text.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-22 00:56:52 UTC
  • mfrom: (6621.2.26 py3_pokes)
  • Revision ID: jelmer@jelmer.uk-20170522005652-yjahcr9hwmjkno7n
Merge Python3 porting work ('py3 pokes')

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
 
 
18
 
"""Text UI, write output to the console.
19
 
"""
 
17
"""Text UI, write output to the console."""
20
18
 
21
19
from __future__ import absolute_import
22
20
 
 
21
import codecs
23
22
import os
24
23
import sys
25
 
import time
 
24
import warnings
26
25
 
27
 
from breezy.lazy_import import lazy_import
 
26
from ..lazy_import import lazy_import
28
27
lazy_import(globals(), """
29
 
import codecs
30
28
import getpass
31
 
import warnings
 
29
import time
32
30
 
33
31
from breezy import (
34
 
    config,
35
32
    debug,
36
33
    progress,
 
34
    )
 
35
""")
 
36
 
 
37
from .. import (
 
38
    config,
37
39
    osutils,
38
40
    trace,
39
41
    )
40
 
 
41
 
""")
42
 
 
43
 
from breezy.ui import (
 
42
from . import (
 
43
    NullProgressView,
44
44
    UIFactory,
45
 
    NullProgressView,
46
45
    )
47
46
 
48
47
 
124
123
            raise KeyboardInterrupt
125
124
        if char == chr(4): # EOF (^d, C-d)
126
125
            raise EOFError
127
 
        return char
 
126
        return char.decode("ascii", "replace")
128
127
 
129
128
    def interact(self):
130
129
        """Keep asking the user until a valid choice is made.
141
140
            try:
142
141
                choice = getchoice()
143
142
            except EOFError:
144
 
                self.ui.stderr.write('\n')
 
143
                self.ui.stderr.write(u'\n')
145
144
                return None
146
145
            except KeyboardInterrupt:
147
 
                self.ui.stderr.write('\n')
148
 
                raise KeyboardInterrupt
 
146
                self.ui.stderr.write(u'\n')
 
147
                raise
149
148
            choice = choice.lower()
150
149
            if choice not in self.alternatives:
151
150
                # Not a valid choice, keep on asking.
152
151
                continue
153
152
            name, index = self.alternatives[choice]
154
153
            if self.echo_back:
155
 
                self.ui.stderr.write(name + '\n')
 
154
                self.ui.stderr.write(name + u'\n')
156
155
            return index
157
156
 
158
157
 
165
164
class TextUIFactory(UIFactory):
166
165
    """A UI factory for Text user interfaces."""
167
166
 
168
 
    def __init__(self,
169
 
                 stdin=None,
170
 
                 stdout=None,
171
 
                 stderr=None):
172
 
        """Create a TextUIFactory.
173
 
        """
 
167
    def __init__(self, stdin, stdout, stderr):
 
168
        """Create a TextUIFactory."""
174
169
        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...
177
170
        self.stdin = stdin
178
171
        self.stdout = stdout
179
172
        self.stderr = stderr
239
232
            if not password:
240
233
                password = None
241
234
            else:
242
 
                password = password.decode(self.stdin.encoding)
243
 
 
244
235
                if password[-1] == '\n':
245
236
                    password = password[:-1]
246
237
        return password
277
268
        if not username:
278
269
            username = None
279
270
        else:
280
 
            username = username.decode(self.stdin.encoding)
281
271
            if username[-1] == '\n':
282
272
                username = username[:-1]
283
273
        return username
301
291
        return NullProgressView()
302
292
 
303
293
    def _make_output_stream_explicit(self, 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)
 
294
        return TextUIOutputStream(self, self.stdout, encoding, encoding_type)
322
295
 
323
296
    def note(self, msg):
324
297
        """Write an already-formatted message, clearing the progress bar if necessary."""
327
300
 
328
301
    def prompt(self, prompt, **kwargs):
329
302
        """Emit prompt on the CLI.
330
 
        
 
303
 
331
304
        :param kwargs: Dictionary of arguments to insert into the prompt,
332
305
            to allow UIs to reformat the prompt.
333
306
        """
334
 
        if type(prompt) != unicode:
 
307
        if not isinstance(prompt, unicode):
335
308
            raise ValueError("prompt %r not a unicode string" % prompt)
336
309
        if kwargs:
337
310
            # See <https://launchpad.net/bugs/365891>
338
311
            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')
346
312
        self.clear_term()
347
313
        self.stdout.flush()
348
314
        self.stderr.write(prompt)
371
337
 
372
338
    def show_warning(self, msg):
373
339
        self.clear_term()
374
 
        if isinstance(msg, unicode):
375
 
            te = osutils.get_terminal_encoding()
376
 
            msg = msg.encode(te, 'replace')
377
340
        self.stderr.write("bzr: warning: %s\n" % msg)
378
341
 
379
342
    def _progress_updated(self, task):
403
366
        # be easier to test; that has a lot of test fallout so for now just
404
367
        # new code can call this
405
368
        if warning_id not in self.suppressed_warnings:
406
 
            self.stderr.write(self.format_user_warning(warning_id, message_args) +
407
 
                '\n')
 
369
            warning = self.format_user_warning(warning_id, message_args)
 
370
            self.stderr.write(warning + '\n')
 
371
 
 
372
 
 
373
def pad_to_width(line, width, encoding_hint='ascii'):
 
374
    """Truncate or pad unicode line to width.
 
375
 
 
376
    This is best-effort for now, and strings containing control codes or
 
377
    non-ascii text may be cut and padded incorrectly.
 
378
    """
 
379
    s = line.encode(encoding_hint, 'replace')
 
380
    return (b'%-*.*s' % (width, width, s)).decode(encoding_hint)
408
381
 
409
382
 
410
383
class TextProgressView(object):
411
384
    """Display of progress bar and other information on a tty.
412
385
 
413
 
    This shows one line of text, including possibly a network indicator, spinner,
414
 
    progress bar, message, etc.
 
386
    This shows one line of text, including possibly a network indicator,
 
387
    spinner, progress bar, message, etc.
415
388
 
416
389
    One instance of this is created and held by the UI, and fed updates when a
417
390
    task wants to be painted.
422
395
    this only prints the stack from the nominated current task up to the root.
423
396
    """
424
397
 
425
 
    def __init__(self, term_file, encoding=None, errors="replace"):
 
398
    def __init__(self, term_file, encoding=None, errors=None):
426
399
        self._term_file = term_file
427
400
        if encoding is None:
428
401
            self._encoding = getattr(term_file, "encoding", None) or "ascii"
429
402
        else:
430
403
            self._encoding = encoding
431
 
        self._encoding_errors = errors
432
404
        # true when there's output on the screen we may need to clear
433
405
        self._have_output = False
434
406
        self._last_transport_msg = ''
443
415
        self._bytes_by_direction = {'unknown': 0, 'read': 0, 'write': 0}
444
416
        self._first_byte_time = None
445
417
        self._fraction = 0
446
 
        # force the progress bar to be off, as at the moment it doesn't 
 
418
        # force the progress bar to be off, as at the moment it doesn't
447
419
        # correspond reliably to overall command progress
448
420
        self.enable_bar = False
449
421
 
450
422
    def _avail_width(self):
451
423
        # we need one extra space for terminals that wrap on last char
452
 
        w = osutils.terminal_width() 
 
424
        w = osutils.terminal_width()
453
425
        if w is None:
454
426
            return None
455
427
        else:
456
428
            return w - 1
457
429
 
458
430
    def _show_line(self, u):
459
 
        s = u.encode(self._encoding, self._encoding_errors)
460
431
        width = self._avail_width()
461
432
        if width is not None:
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')
 
433
            u = pad_to_width(u, width, encoding_hint=self._encoding)
 
434
        self._term_file.write('\r' + u + '\r')
466
435
 
467
436
    def clear(self):
468
437
        if self._have_output:
489
458
                    self._last_task._overall_completion_fraction() or 0
490
459
            if (completion_fraction < self._fraction and 'progress' in
491
460
                debug.debug_flags):
492
 
                import pdb;pdb.set_trace()
 
461
                debug.set_trace()
493
462
            self._fraction = completion_fraction
494
463
            markers = int(round(float(cols) * completion_fraction)) - 1
495
464
            bar_str = '[' + ('#' * markers + spin_str).ljust(cols) + '] '
545
514
        if avail_width is not None:
546
515
            # if terminal avail_width is unknown, don't truncate
547
516
            current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
 
517
            # GZ 2017-04-22: Should measure and truncate task_part properly
548
518
            gap = current_len - avail_width
549
519
            if gap > 0:
550
520
                task_part = task_part[:-gap-2] + '..'
563
533
 
564
534
    def show_progress(self, task):
565
535
        """Called by the task object when it has changed.
566
 
        
567
 
        :param task: The top task object; its parents are also included 
 
536
 
 
537
        :param task: The top task object; its parents are also included
568
538
            by following links.
569
539
        """
570
540
        must_update = task is not self._last_task
657
627
            self._term_file.write(msg + '\n')
658
628
 
659
629
 
 
630
def _get_stream_encoding(stream):
 
631
    encoding = config.GlobalStack().get('output_encoding')
 
632
    if encoding is None:
 
633
        encoding = getattr(stream, "encoding", None)
 
634
    if encoding is None:
 
635
        encoding = osutils.get_terminal_encoding(trace=True)
 
636
    return encoding
 
637
 
 
638
 
 
639
def _unwrap_stream(stream):
 
640
    inner = getattr(stream, "buffer", None)
 
641
    if inner is None:
 
642
        inner = getattr(stream, "stream", None)
 
643
    return inner
 
644
 
 
645
 
 
646
def _wrap_in_stream(stream, encoding=None, errors='replace'):
 
647
    if encoding is None:
 
648
        encoding = _get_stream_encoding(stream)
 
649
    encoded_stream = codecs.getreader(encoding)(stream, errors=errors)
 
650
    encoded_stream.encoding = encoding
 
651
    return encoded_stream
 
652
 
 
653
 
 
654
def _wrap_out_stream(stream, encoding=None, errors='replace'):
 
655
    if encoding is None:
 
656
        encoding = _get_stream_encoding(stream)
 
657
    encoded_stream = codecs.getwriter(encoding)(stream, errors=errors)
 
658
    encoded_stream.encoding = encoding
 
659
    return encoded_stream
 
660
 
 
661
 
660
662
class TextUIOutputStream(object):
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.
 
663
    """Decorates stream to interact better with progress and change encoding.
 
664
 
 
665
    Before writing to the wrapped stream, progress is cleared. Callers must
 
666
    ensure bulk output is terminated with a newline so progress won't overwrite
 
667
    partial lines.
 
668
 
 
669
    Additionally, the encoding and errors behaviour of the underlying stream
 
670
    can be changed at this point. If errors is set to 'exact' raw bytes may be
 
671
    written to the underlying stream.
665
672
    """
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):
 
673
 
 
674
    def __init__(self, ui_factory, stream, encoding=None, errors='strict'):
674
675
        self.ui_factory = ui_factory
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)
 
676
        # GZ 2017-05-21: Clean up semantics when callers are made saner.
 
677
        inner = _unwrap_stream(stream)
 
678
        self.raw_stream = None
 
679
        if errors == "exact":
 
680
            errors = "strict"
 
681
            self.raw_stream = inner
 
682
        if inner is None:
 
683
            self.wrapped_stream = stream
 
684
            if encoding is None:
 
685
                encoding = _get_stream_encoding(stream)
 
686
        else:
 
687
            self.wrapped_stream = _wrap_out_stream(inner, encoding, errors)
 
688
            if encoding is None:
 
689
                encoding = self.wrapped_stream.encoding
 
690
        self.encoding = encoding
 
691
        self.errors = errors
 
692
 
 
693
    def _write(self, to_write):
 
694
        if isinstance(to_write, bytes):
 
695
            try:
 
696
                to_write = to_write.decode(self.encoding, self.errors)
 
697
            except UnicodeDecodeError:
 
698
                self.raw_stream.write(to_write)
 
699
                return
 
700
        self.wrapped_stream.write(to_write)
680
701
 
681
702
    def flush(self):
682
703
        self.ui_factory.clear_term()
684
705
 
685
706
    def write(self, to_write):
686
707
        self.ui_factory.clear_term()
687
 
        self.wrapped_stream.write(to_write)
 
708
        self._write(to_write)
688
709
 
689
710
    def writelines(self, lines):
690
711
        self.ui_factory.clear_term()
691
 
        self.wrapped_stream.writelines(lines)
 
712
        for line in lines:
 
713
            self._write(line)