Rev 4880: (mbp) progress bars automatically synchronize with other terminal in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Wed Dec 9 06:37:41 GMT 2009
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 4880 [merge]
revision-id: pqm at pqm.ubuntu.com-20091209063740-orfojzx53lbt29ey
parent: pqm at pqm.ubuntu.com-20091209025342-sidvxfcqdgxmuz59
parent: mbp at sourcefrog.net-20091209054732-7414e9uma23mfv6x
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Wed 2009-12-09 06:37:40 +0000
message:
(mbp) progress bars automatically synchronize with other terminal
output
modified:
NEWS NEWS-20050323055033-4e00b5db738777ff
bzrlib/builtins.py builtins.py-20050830033751-fc01482b9ca23183
bzrlib/cmd_version_info.py __init__.py-20051228204928-697d01fdca29c99b
bzrlib/commands.py bzr.py-20050309040720-d10f4714595cf8c3
bzrlib/log.py log.py-20050505065812-c40ce11702fe5fb1
bzrlib/tests/per_uifactory/__init__.py __init__.py-20090923045301-o12zypjwsidxn2hy-1
bzrlib/tests/test_ui.py test_ui.py-20051130162854-458e667a7414af09
bzrlib/ui/__init__.py ui.py-20050824083933-8cf663c763ba53a9
bzrlib/ui/text.py text.py-20051130153916-2e438cffc8afc478
=== modified file 'NEWS'
--- a/NEWS 2009-12-07 22:32:56 +0000
+++ b/NEWS 2009-12-09 05:47:32 +0000
@@ -241,6 +241,12 @@
* Include Japanese translations for documentation (Inada Naoki)
+* New API ``ui_factory.make_output_stream`` to be used for sending bulk
+ (rather than user-interaction) data to stdout. This automatically
+ coordinates with progress bars or other terminal activity, and can be
+ overridden by GUIs.
+ (Martin Pool, 493944)
+
Internals
*********
=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py 2009-12-08 17:30:11 +0000
+++ b/bzrlib/builtins.py 2009-12-09 05:47:32 +0000
@@ -2349,7 +2349,10 @@
# Build the log formatter
if log_format is None:
log_format = log.log_formatter_registry.get_default(b)
+ # Make a non-encoding output to include the diffs - bug 328007
+ unencoded_output = ui.ui_factory.make_output_stream(encoding_type='exact')
lf = log_format(show_ids=show_ids, to_file=self.outf,
+ to_exact_file=unencoded_output,
show_timezone=timezone,
delta_format=get_verbosity_level(),
levels=levels,
=== modified file 'bzrlib/cmd_version_info.py'
--- a/bzrlib/cmd_version_info.py 2009-03-23 14:59:43 +0000
+++ b/bzrlib/cmd_version_info.py 2009-11-16 02:26:32 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005, 2006 Canonical Ltd
+# Copyright (C) 2005, 2006, 2009 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,14 +17,17 @@
"""Commands for generating snapshot information about a bzr tree."""
from bzrlib.lazy_import import lazy_import
+
lazy_import(globals(), """
from bzrlib import (
branch,
errors,
+ ui,
+ version_info_formats,
workingtree,
- version_info_formats,
)
""")
+
from bzrlib.commands import Command
from bzrlib.option import Option, RegistryOption
@@ -113,4 +116,4 @@
include_revision_history=include_history,
include_file_revisions=include_file_revisions,
template=template)
- builder.generate(self.outf)
+ builder.generate(ui.ui_factory.make_output_stream())
=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py 2009-12-02 07:56:16 +0000
+++ b/bzrlib/commands.py 2009-12-09 05:47:32 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2008 Canonical Ltd
+# Copyright (C) 2006, 2008, 2009 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -45,6 +45,7 @@
option,
osutils,
trace,
+ ui,
win32utils,
)
""")
@@ -595,26 +596,8 @@
def _setup_outf(self):
"""Return a file linked to stdout, which has proper encoding."""
- # Originally I was using self.stdout, but that looks
- # *way* too much like sys.stdout
- if self.encoding_type == 'exact':
- # force sys.stdout to be binary stream on win32
- if sys.platform == 'win32':
- fileno = getattr(sys.stdout, 'fileno', None)
- if fileno:
- import msvcrt
- msvcrt.setmode(fileno(), os.O_BINARY)
- self.outf = sys.stdout
- return
-
- output_encoding = osutils.get_terminal_encoding()
-
- self.outf = codecs.getwriter(output_encoding)(sys.stdout,
- errors=self.encoding_type)
- # For whatever reason codecs.getwriter() does not advertise its encoding
- # it just returns the encoding of the wrapped file, which is completely
- # bogus. So set the attribute, so we can find the correct encoding later.
- self.outf.encoding = output_encoding
+ self.outf = ui.ui_factory.make_output_stream(
+ encoding_type=self.encoding_type)
def run_argv_aliases(self, argv, alias_argv=None):
"""Parse the command line and run with extra aliases in alias_argv."""
=== modified file 'bzrlib/log.py'
--- a/bzrlib/log.py 2009-12-04 22:13:52 +0000
+++ b/bzrlib/log.py 2009-12-09 05:47:32 +0000
@@ -1293,10 +1293,13 @@
preferred_levels = 0
def __init__(self, to_file, show_ids=False, show_timezone='original',
- delta_format=None, levels=None, show_advice=False):
+ delta_format=None, levels=None, show_advice=False,
+ to_exact_file=None):
"""Create a LogFormatter.
:param to_file: the file to output to
+ :param to_exact_file: if set, gives an output stream to which
+ non-Unicode diffs are written.
:param show_ids: if True, revision-ids are to be displayed
:param show_timezone: the timezone to use
:param delta_format: the level of delta information to display
@@ -1309,7 +1312,13 @@
self.to_file = to_file
# 'exact' stream used to show diff, it should print content 'as is'
# and should not try to decode/encode it to unicode to avoid bug #328007
- self.to_exact_file = getattr(to_file, 'stream', to_file)
+ if to_exact_file is not None:
+ self.to_exact_file = to_exact_file
+ else:
+ # XXX: somewhat hacky; this assumes it's a codec writer; it's better
+ # for code that expects to get diffs to pass in the exact file
+ # stream
+ self.to_exact_file = getattr(to_file, 'stream', to_file)
self.show_ids = show_ids
self.show_timezone = show_timezone
if delta_format is None:
@@ -1494,9 +1503,11 @@
short_status=False)
if revision.diff is not None:
to_file.write(indent + 'diff:\n')
+ to_file.flush()
# Note: we explicitly don't indent the diff (relative to the
# revision information) so that the output can be fed to patch -p0
self.show_diff(self.to_exact_file, revision.diff, indent)
+ self.to_exact_file.flush()
def get_advice_separator(self):
"""Get the text separating the log from the closing advice."""
=== modified file 'bzrlib/tests/per_uifactory/__init__.py'
--- a/bzrlib/tests/per_uifactory/__init__.py 2009-09-23 06:29:46 +0000
+++ b/bzrlib/tests/per_uifactory/__init__.py 2009-11-16 01:18:03 +0000
@@ -74,6 +74,15 @@
self.factory.show_warning(msg)
self._check_show_warning(msg)
+ def test_make_output_stream(self):
+ # at the moment this is only implemented on text uis; i'm not sure
+ # what it should do elsewhere
+ try:
+ output_stream = self.factory.make_output_stream()
+ except NotImplementedError, e:
+ raise tests.TestSkipped(str(e))
+ output_stream.write('hello!')
+
class TestTextUIFactory(tests.TestCase, UIFactoryTestMixin):
=== modified file 'bzrlib/tests/test_ui.py'
--- a/bzrlib/tests/test_ui.py 2009-10-15 20:04:37 +0000
+++ b/bzrlib/tests/test_ui.py 2009-11-16 02:56:34 +0000
@@ -50,6 +50,7 @@
NullProgressView,
TextProgressView,
TextUIFactory,
+ TextUIOutputStream,
)
@@ -253,6 +254,32 @@
pb.finished()
+class TestTextUIOutputStream(TestCase):
+ """Tests for output stream that synchronizes with progress bar."""
+
+ def test_output_clears_terminal(self):
+ stdout = StringIO()
+ stderr = StringIO()
+ clear_calls = []
+
+ uif = TextUIFactory(None, stdout, stderr)
+ uif.clear_term = lambda: clear_calls.append('clear')
+
+ stream = TextUIOutputStream(uif, uif.stdout)
+ stream.write("Hello world!\n")
+ stream.write("there's more...\n")
+ stream.writelines(["1\n", "2\n", "3\n"])
+
+ self.assertEqual(stdout.getvalue(),
+ "Hello world!\n"
+ "there's more...\n"
+ "1\n2\n3\n")
+ self.assertEqual(['clear', 'clear', 'clear'],
+ clear_calls)
+
+ stream.flush()
+
+
class UITests(tests.TestCase):
def test_progress_construction(self):
=== modified file 'bzrlib/ui/__init__.py'
--- a/bzrlib/ui/__init__.py 2009-12-04 10:24:28 +0000
+++ b/bzrlib/ui/__init__.py 2009-12-09 05:47:32 +0000
@@ -125,6 +125,34 @@
"""
raise NotImplementedError(self.get_password)
+ def make_output_stream(self, encoding=None, encoding_type=None):
+ """Get a stream for sending out bulk text data.
+
+ This is used for commands that produce bulk text, such as log or diff
+ output, as opposed to user interaction. This should work even for
+ non-interactive user interfaces. Typically this goes to a decorated
+ version of stdout, but in a GUI it might be appropriate to send it to a
+ window displaying the text.
+
+ :param encoding: Unicode encoding for output; default is the
+ terminal encoding, which may be different from the user encoding.
+ (See get_terminal_encoding.)
+
+ :param encoding_type: How to handle encoding errors:
+ replace/strict/escape/exact. Default is replace.
+ """
+ # XXX: is the caller supposed to close the resulting object?
+ if encoding is None:
+ encoding = osutils.get_terminal_encoding()
+ if encoding_type is None:
+ encoding_type = 'replace'
+ out_stream = self._make_output_stream_explicit(encoding, encoding_type)
+ return out_stream
+
+ def _make_output_stream_explicit(self, encoding, encoding_type):
+ raise NotImplementedError("%s doesn't support make_output_stream"
+ % (self.__class__.__name__))
+
def nested_progress_bar(self):
"""Return a nested progress bar.
=== modified file 'bzrlib/ui/text.py'
--- a/bzrlib/ui/text.py 2009-12-07 10:38:09 +0000
+++ b/bzrlib/ui/text.py 2009-12-09 05:47:32 +0000
@@ -18,6 +18,7 @@
"""Text UI, write output to the console.
"""
+import codecs
import getpass
import os
import sys
@@ -146,6 +147,26 @@
else:
return NullProgressView()
+ def _make_output_stream_explicit(self, encoding, encoding_type):
+ if encoding_type == 'exact':
+ # force sys.stdout to be binary stream on win32;
+ # NB: this leaves the file set in that mode; may cause problems if
+ # one process tries to do binary and then text output
+ if sys.platform == 'win32':
+ fileno = getattr(self.stdout, 'fileno', None)
+ if fileno:
+ import msvcrt
+ msvcrt.setmode(fileno(), os.O_BINARY)
+ return TextUIOutputStream(self, self.stdout)
+ else:
+ encoded_stdout = codecs.getwriter(encoding)(self.stdout,
+ errors=encoding_type)
+ # For whatever reason codecs.getwriter() does not advertise its encoding
+ # it just returns the encoding of the wrapped file, which is completely
+ # bogus. So set the attribute, so we can find the correct encoding later.
+ encoded_stdout.encoding = encoding
+ return TextUIOutputStream(self, encoded_stdout)
+
def note(self, msg):
"""Write an already-formatted message, clearing the progress bar if necessary."""
self.clear_term()
@@ -367,3 +388,37 @@
self._bytes_since_update = 0
self._last_transport_msg = msg
self._repaint()
+
+
+class TextUIOutputStream(object):
+ """Decorates an output stream so that the terminal is cleared before writing.
+
+ This is supposed to ensure that the progress bar does not conflict with bulk
+ text output.
+ """
+ # XXX: this does not handle the case of writing part of a line, then doing
+ # progress bar output: the progress bar will probably write over it.
+ # one option is just to buffer that text until we have a full line;
+ # another is to save and restore it
+
+ # XXX: might need to wrap more methods
+
+ def __init__(self, ui_factory, wrapped_stream):
+ self.ui_factory = ui_factory
+ self.wrapped_stream = wrapped_stream
+ # this does no transcoding, but it must expose the underlying encoding
+ # because some callers need to know what can be written - see for
+ # example unescape_for_display.
+ self.encoding = getattr(wrapped_stream, 'encoding', None)
+
+ def flush(self):
+ self.ui_factory.clear_term()
+ self.wrapped_stream.flush()
+
+ def write(self, to_write):
+ self.ui_factory.clear_term()
+ self.wrapped_stream.write(to_write)
+
+ def writelines(self, lines):
+ self.ui_factory.clear_term()
+ self.wrapped_stream.writelines(lines)
More information about the bazaar-commits
mailing list