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