Merge lp:~mbp/bzr/progress-output into lp:bzr
- progress-output
- Merge into bzr.dev
Status: | Merged |
---|---|
Approved by: | John A Meinel |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~mbp/bzr/progress-output |
Merge into: | lp:bzr |
Diff against target: |
340 lines (+151/-26) 9 files modified
NEWS (+6/-0) bzrlib/builtins.py (+3/-0) bzrlib/cmd_version_info.py (+6/-3) bzrlib/commands.py (+4/-21) bzrlib/log.py (+13/-2) bzrlib/tests/per_uifactory/__init__.py (+9/-0) bzrlib/tests/test_ui.py (+27/-0) bzrlib/ui/__init__.py (+28/-0) bzrlib/ui/text.py (+55/-0) |
To merge this branch: | bzr merge lp:~mbp/bzr/progress-output |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Approve | ||
Ian Clatworthy | Approve | ||
Vincent Ladeuil | Pending | ||
Review via email: mp+14944@code.launchpad.net |
Commit message
Description of the change
Martin Pool (mbp) wrote : | # |
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/progress-output into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
>
> This conceptually relates to <https:/
>
> +* New API ``ui_factory.
> + (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)
> +
>
> The idea is to
>
> * Allow us to show transport activity whenever it happens, without clobbering the output of eg log - this doesn't actually restore this behaviour, but it gets ready for it.
>
> * Move away from Command.outf, which assumes that it's only the command layer that knows about stdout. In fact, it's generally a layer or two down, and passing what's really a global is poor.
>
> * Also avoid code defaulting directly to sys.stdout.
>
> * In particular, put the unicode-handling features of outf into a more reusable place.
>
> * Give GUIs a way to capture bulk output of application-level commands to show in a window.
>
> Command.outf still works and redirects into this.
>
+ def test_make_
+ # at the moment this is only implemented on text uis; i'm not sure
+ # what it should do elsewhere
+ try:
+ output_stream = self.factory.
+ except NotImplementedE
+ raise tests.TestSkipp
+ output_
+
^- Since Command._setup_outf is now relying on "make_output_
they allowed to not implement it?
+class TestTextUIOutpu
+ """Tests for output stream that synchronizes with progress bar."""
+
+ def test_output_
+ stdout = StringIO()
+ stderr = StringIO()
+ clear_calls = []
+
+ uif = TextUIFactory(None, stdout, stderr)
+ uif.clear_term = lambda: clear_calls.
+
+ stream = TextUIOutputStr
+ stream.write("Hello world!\n")
+ stream.
+ stream.
+
+ self.assertEqua
+ "Hello world!\n"
+ "there's more...\n"
+ "1\n2\n3\n")
+ self.assertEqua
+ clear_calls)
+
+ stream.flush()
+
+
^- This seems like a reasonable start. Though I personally am much more
interested in what happens if you pass Unicode bytes to this stream.
Especially in the "bzr log -p" or "bzr diff" case, which tends to mix
Unicode bytes with raw content from files.
I realize you do have an 'encoding_type' flag...
+ # 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.
+ out_stream = self._make_
Gordon Tyler (doxxx) wrote : | # |
I'd like to see this move forward since I would be making use of the updated UIFactory for my output-cleanup branch.
Martin Pool (mbp) wrote : | # |
> + def test_make_
> + # at the moment this is only implemented on text uis; i'm not sure
> + # what it should do elsewhere
> + try:
> + output_stream = self.factory.
> + except NotImplementedE
> + raise tests.TestSkipp
> + output_
> +
>
> ^- Since Command._setup_outf is now relying on "make_output_
> they allowed to not implement it?
It does mean that you won't be able to run Command code under any other UIFactory. I think probably it would still be reasonable to have a totally silent one that just can't run that kind of code. At least I think it's ok for now.
>
>
>
> +class TestTextUIOutpu
> + """Tests for output stream that synchronizes with progress bar."""
> +
> + def test_output_
> + stdout = StringIO()
> + stderr = StringIO()
> + clear_calls = []
> +
> + uif = TextUIFactory(None, stdout, stderr)
> + uif.clear_term = lambda: clear_calls.
> +
> + stream = TextUIOutputStr
> + stream.write("Hello world!\n")
> + stream.
> + stream.
> +
> + self.assertEqua
> + "Hello world!\n"
> + "there's more...\n"
> + "1\n2\n3\n")
> + self.assertEqua
> + clear_calls)
> +
> + stream.flush()
> +
> +
>
> ^- This seems like a reasonable start. Though I personally am much more
> interested in what happens if you pass Unicode bytes to this stream.
> Especially in the "bzr log -p" or "bzr diff" case, which tends to mix
> Unicode bytes with raw content from files.
>
> I realize you do have an 'encoding_type' flag...
At the moment the encoding type requires you to choose up front between either dealing only in bytes, or dealing only in unicode. (Or byte strings that contain only ascii can be accepted for either of course.)
I think eventually you would want to mix them as you outline below, but this is the same constraint the existing code has.
>
>
> + # 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.
> + out_stream = self._make_
> encoding_type)
> + if not getattr(out_stream, 'encoding', None):
> + out_stream.encoding = encoding
> +
>
> ^- The comment doesn't fit the action. It says that "w =
> codecs.getwriter()" *does* have an 'encoding' attribute, but it returns
> the value of the wrapped object. IOW
>
> w = codecs.
> w.encoding == sys.stdout.encoding
>
>
> rather than
> w.encoding == 'utf-8'
>
> I may be misunderstanding something, though. And if this was the code
> that was originally present, than it makes sense to preserve the behavior.
It was copied code. I might see if it can just be re...
Martin Pool (mbp) wrote : | # |
This has a test failure in a non_ascii diff test. I thought it had all passed before, but perhaps not.
John A Meinel (jameinel) wrote : | # |
Does that mean we should be bumping it back to "Work In Progress"?
I guess my concern is that if we set it as such, then it will never get seen again...
Ian Clatworthy (ian-clatworthy) wrote : | # |
This looks clean enough to merge to me. John raises a bunch of interesting questions though so please double check he's happy before landing it.
My only comment is that write() and writelines() could do with docstrings mentioning that the caller is responsible for providing newlines for each line.
Gordon Tyler (doxxx) wrote : | # |
> My only comment is that write() and writelines() could do with docstrings
> mentioning that the caller is responsible for providing newlines for each
> line.
My first reaction was that this sounds like highly unintuitive API. But then I went and read up on Python's file object API and it seems it does the same thing. So I guess it's expected then.
Martin Pool (mbp) wrote : | # |
Back to wip; in-progress bug https:/
Martin Pool (mbp) wrote : | # |
@john I moved the tweak for codecs.
The failure I'm seeing now is the same as in <https:/
So this is a bit annoying, but possibly also a chance to fix that bug more cleanly.
That bug has a somewhat hackish fix based on peeking inside EncodingWriters. I'm wondering if we should actually pass in an exact encoding file stream that it can use for the diffs, which would make things incrementally cleaner.
Martin Pool (mbp) wrote : | # |
Looking at that test and bug: this is a clear case where we want to mix unicode and binary output. If you have different objects you (perhaps) need to synchronize them by flushing manually; if you have one object it may still need to flush the codec writers but it could be hidden from more callers.
If it was write_bytes() vs write_unicode() it would be more obvious in the code whether people were using the right one, vs needing to work out what kind of stream they have.
unescape_
Martin Pool (mbp) wrote : | # |
Quick re-review requested; here's the incremental diff
=== modified file 'bzrlib/
--- bzrlib/builtins.py 2009-11-04 22:32:13 +0000
+++ bzrlib/builtins.py 2009-12-08 09:06:02 +0000
@@ -2323,7 +2323,10 @@
# Build the log formatter
if log_format is None:
+ # Make a non-encoding output to include the diffs - bug 328007
+ unencoded_output = ui.ui_factory.
lf = log_format(
+ to_exact_
=== modified file 'bzrlib/log.py'
--- bzrlib/log.py 2009-10-29 03:32:42 +0000
+++ bzrlib/log.py 2009-12-08 09:17:21 +0000
@@ -1291,10 +1291,13 @@
preferred_
def __init__(self, to_file, show_ids=False, show_timezone=
- delta_format=None, levels=None, show_advice=False):
+ delta_format=None, levels=None, show_advice=False,
+ to_exact_
"""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
@@ -1307,7 +1310,13 @@
# '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)
if delta_format is None:
@@ -1463,9 +1472,11 @@
if revision.diff is not None:
+ 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.to_
def get_advice_
"""Get the text separating the log from the closing advice."""
=== modified file 'bzrlib/
--- bzrlib/
+++ bzrlib/
@@ -146,12 +146,7 @@
if encoding_type i...
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Martin Pool wrote:
> Quick re-review requested; here's the incremental diff
>
> === modified file 'bzrlib/
> --- bzrlib/builtins.py 2009-11-04 22:32:13 +0000
> +++ bzrlib/builtins.py 2009-12-08 09:06:02 +0000
> @@ -2323,7 +2323,10 @@
> # Build the log formatter
> if log_format is None:
> log_format = log.log_
> + # Make a non-encoding output to include the diffs - bug 328007
> + unencoded_output = ui.ui_factory.
> lf = log_format(
> + to_exact_
> show_timezone=
> delta_format=
> levels=levels,
>
> === modified file 'bzrlib/log.py'
> --- bzrlib/log.py 2009-10-29 03:32:42 +0000
> +++ bzrlib/log.py 2009-12-08 09:17:21 +0000
> @@ -1291,10 +1291,13 @@
> preferred_levels = 0
>
> def __init__(self, to_file, show_ids=False, show_timezone=
> - delta_format=None, levels=None, show_advice=False):
> + delta_format=None, levels=None, show_advice=False,
> + to_exact_
> """Create a LogFormatter.
>
Having to explicitly flush one before writing to the other is a bit
strange, and certainly the "write_bytes()" versus "write_unicode()"
makes that cleaner (IMO).
But this is a good incremental improvement over where we are at today.
review: approve
merge: approve
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAks
dlEAniXiLRPa3I6
=VhDt
-----END PGP SIGNATURE-----
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-12-07 22:32:56 +0000 |
3 | +++ NEWS 2009-12-09 05:49:10 +0000 |
4 | @@ -241,6 +241,12 @@ |
5 | |
6 | * Include Japanese translations for documentation (Inada Naoki) |
7 | |
8 | +* New API ``ui_factory.make_output_stream`` to be used for sending bulk |
9 | + (rather than user-interaction) data to stdout. This automatically |
10 | + coordinates with progress bars or other terminal activity, and can be |
11 | + overridden by GUIs. |
12 | + (Martin Pool, 493944) |
13 | + |
14 | Internals |
15 | ********* |
16 | |
17 | |
18 | === modified file 'bzrlib/builtins.py' |
19 | --- bzrlib/builtins.py 2009-12-08 17:30:11 +0000 |
20 | +++ bzrlib/builtins.py 2009-12-09 05:49:10 +0000 |
21 | @@ -2349,7 +2349,10 @@ |
22 | # Build the log formatter |
23 | if log_format is None: |
24 | log_format = log.log_formatter_registry.get_default(b) |
25 | + # Make a non-encoding output to include the diffs - bug 328007 |
26 | + unencoded_output = ui.ui_factory.make_output_stream(encoding_type='exact') |
27 | lf = log_format(show_ids=show_ids, to_file=self.outf, |
28 | + to_exact_file=unencoded_output, |
29 | show_timezone=timezone, |
30 | delta_format=get_verbosity_level(), |
31 | levels=levels, |
32 | |
33 | === modified file 'bzrlib/cmd_version_info.py' |
34 | --- bzrlib/cmd_version_info.py 2009-03-23 14:59:43 +0000 |
35 | +++ bzrlib/cmd_version_info.py 2009-12-09 05:49:10 +0000 |
36 | @@ -1,4 +1,4 @@ |
37 | -# Copyright (C) 2005, 2006 Canonical Ltd |
38 | +# Copyright (C) 2005, 2006, 2009 Canonical Ltd |
39 | # |
40 | # This program is free software; you can redistribute it and/or modify |
41 | # it under the terms of the GNU General Public License as published by |
42 | @@ -17,14 +17,17 @@ |
43 | """Commands for generating snapshot information about a bzr tree.""" |
44 | |
45 | from bzrlib.lazy_import import lazy_import |
46 | + |
47 | lazy_import(globals(), """ |
48 | from bzrlib import ( |
49 | branch, |
50 | errors, |
51 | + ui, |
52 | + version_info_formats, |
53 | workingtree, |
54 | - version_info_formats, |
55 | ) |
56 | """) |
57 | + |
58 | from bzrlib.commands import Command |
59 | from bzrlib.option import Option, RegistryOption |
60 | |
61 | @@ -113,4 +116,4 @@ |
62 | include_revision_history=include_history, |
63 | include_file_revisions=include_file_revisions, |
64 | template=template) |
65 | - builder.generate(self.outf) |
66 | + builder.generate(ui.ui_factory.make_output_stream()) |
67 | |
68 | === modified file 'bzrlib/commands.py' |
69 | --- bzrlib/commands.py 2009-12-02 07:56:16 +0000 |
70 | +++ bzrlib/commands.py 2009-12-09 05:49:10 +0000 |
71 | @@ -1,4 +1,4 @@ |
72 | -# Copyright (C) 2006, 2008 Canonical Ltd |
73 | +# Copyright (C) 2006, 2008, 2009 Canonical Ltd |
74 | # |
75 | # This program is free software; you can redistribute it and/or modify |
76 | # it under the terms of the GNU General Public License as published by |
77 | @@ -45,6 +45,7 @@ |
78 | option, |
79 | osutils, |
80 | trace, |
81 | + ui, |
82 | win32utils, |
83 | ) |
84 | """) |
85 | @@ -595,26 +596,8 @@ |
86 | |
87 | def _setup_outf(self): |
88 | """Return a file linked to stdout, which has proper encoding.""" |
89 | - # Originally I was using self.stdout, but that looks |
90 | - # *way* too much like sys.stdout |
91 | - if self.encoding_type == 'exact': |
92 | - # force sys.stdout to be binary stream on win32 |
93 | - if sys.platform == 'win32': |
94 | - fileno = getattr(sys.stdout, 'fileno', None) |
95 | - if fileno: |
96 | - import msvcrt |
97 | - msvcrt.setmode(fileno(), os.O_BINARY) |
98 | - self.outf = sys.stdout |
99 | - return |
100 | - |
101 | - output_encoding = osutils.get_terminal_encoding() |
102 | - |
103 | - self.outf = codecs.getwriter(output_encoding)(sys.stdout, |
104 | - errors=self.encoding_type) |
105 | - # For whatever reason codecs.getwriter() does not advertise its encoding |
106 | - # it just returns the encoding of the wrapped file, which is completely |
107 | - # bogus. So set the attribute, so we can find the correct encoding later. |
108 | - self.outf.encoding = output_encoding |
109 | + self.outf = ui.ui_factory.make_output_stream( |
110 | + encoding_type=self.encoding_type) |
111 | |
112 | def run_argv_aliases(self, argv, alias_argv=None): |
113 | """Parse the command line and run with extra aliases in alias_argv.""" |
114 | |
115 | === modified file 'bzrlib/log.py' |
116 | --- bzrlib/log.py 2009-12-04 22:13:52 +0000 |
117 | +++ bzrlib/log.py 2009-12-09 05:49:10 +0000 |
118 | @@ -1293,10 +1293,13 @@ |
119 | preferred_levels = 0 |
120 | |
121 | def __init__(self, to_file, show_ids=False, show_timezone='original', |
122 | - delta_format=None, levels=None, show_advice=False): |
123 | + delta_format=None, levels=None, show_advice=False, |
124 | + to_exact_file=None): |
125 | """Create a LogFormatter. |
126 | |
127 | :param to_file: the file to output to |
128 | + :param to_exact_file: if set, gives an output stream to which |
129 | + non-Unicode diffs are written. |
130 | :param show_ids: if True, revision-ids are to be displayed |
131 | :param show_timezone: the timezone to use |
132 | :param delta_format: the level of delta information to display |
133 | @@ -1309,7 +1312,13 @@ |
134 | self.to_file = to_file |
135 | # 'exact' stream used to show diff, it should print content 'as is' |
136 | # and should not try to decode/encode it to unicode to avoid bug #328007 |
137 | - self.to_exact_file = getattr(to_file, 'stream', to_file) |
138 | + if to_exact_file is not None: |
139 | + self.to_exact_file = to_exact_file |
140 | + else: |
141 | + # XXX: somewhat hacky; this assumes it's a codec writer; it's better |
142 | + # for code that expects to get diffs to pass in the exact file |
143 | + # stream |
144 | + self.to_exact_file = getattr(to_file, 'stream', to_file) |
145 | self.show_ids = show_ids |
146 | self.show_timezone = show_timezone |
147 | if delta_format is None: |
148 | @@ -1494,9 +1503,11 @@ |
149 | short_status=False) |
150 | if revision.diff is not None: |
151 | to_file.write(indent + 'diff:\n') |
152 | + to_file.flush() |
153 | # Note: we explicitly don't indent the diff (relative to the |
154 | # revision information) so that the output can be fed to patch -p0 |
155 | self.show_diff(self.to_exact_file, revision.diff, indent) |
156 | + self.to_exact_file.flush() |
157 | |
158 | def get_advice_separator(self): |
159 | """Get the text separating the log from the closing advice.""" |
160 | |
161 | === modified file 'bzrlib/tests/per_uifactory/__init__.py' |
162 | --- bzrlib/tests/per_uifactory/__init__.py 2009-09-23 06:29:46 +0000 |
163 | +++ bzrlib/tests/per_uifactory/__init__.py 2009-12-09 05:49:10 +0000 |
164 | @@ -74,6 +74,15 @@ |
165 | self.factory.show_warning(msg) |
166 | self._check_show_warning(msg) |
167 | |
168 | + def test_make_output_stream(self): |
169 | + # at the moment this is only implemented on text uis; i'm not sure |
170 | + # what it should do elsewhere |
171 | + try: |
172 | + output_stream = self.factory.make_output_stream() |
173 | + except NotImplementedError, e: |
174 | + raise tests.TestSkipped(str(e)) |
175 | + output_stream.write('hello!') |
176 | + |
177 | |
178 | class TestTextUIFactory(tests.TestCase, UIFactoryTestMixin): |
179 | |
180 | |
181 | === modified file 'bzrlib/tests/test_ui.py' |
182 | --- bzrlib/tests/test_ui.py 2009-10-15 20:04:37 +0000 |
183 | +++ bzrlib/tests/test_ui.py 2009-12-09 05:49:10 +0000 |
184 | @@ -50,6 +50,7 @@ |
185 | NullProgressView, |
186 | TextProgressView, |
187 | TextUIFactory, |
188 | + TextUIOutputStream, |
189 | ) |
190 | |
191 | |
192 | @@ -253,6 +254,32 @@ |
193 | pb.finished() |
194 | |
195 | |
196 | +class TestTextUIOutputStream(TestCase): |
197 | + """Tests for output stream that synchronizes with progress bar.""" |
198 | + |
199 | + def test_output_clears_terminal(self): |
200 | + stdout = StringIO() |
201 | + stderr = StringIO() |
202 | + clear_calls = [] |
203 | + |
204 | + uif = TextUIFactory(None, stdout, stderr) |
205 | + uif.clear_term = lambda: clear_calls.append('clear') |
206 | + |
207 | + stream = TextUIOutputStream(uif, uif.stdout) |
208 | + stream.write("Hello world!\n") |
209 | + stream.write("there's more...\n") |
210 | + stream.writelines(["1\n", "2\n", "3\n"]) |
211 | + |
212 | + self.assertEqual(stdout.getvalue(), |
213 | + "Hello world!\n" |
214 | + "there's more...\n" |
215 | + "1\n2\n3\n") |
216 | + self.assertEqual(['clear', 'clear', 'clear'], |
217 | + clear_calls) |
218 | + |
219 | + stream.flush() |
220 | + |
221 | + |
222 | class UITests(tests.TestCase): |
223 | |
224 | def test_progress_construction(self): |
225 | |
226 | === modified file 'bzrlib/ui/__init__.py' |
227 | --- bzrlib/ui/__init__.py 2009-12-04 10:24:28 +0000 |
228 | +++ bzrlib/ui/__init__.py 2009-12-09 05:49:10 +0000 |
229 | @@ -125,6 +125,34 @@ |
230 | """ |
231 | raise NotImplementedError(self.get_password) |
232 | |
233 | + def make_output_stream(self, encoding=None, encoding_type=None): |
234 | + """Get a stream for sending out bulk text data. |
235 | + |
236 | + This is used for commands that produce bulk text, such as log or diff |
237 | + output, as opposed to user interaction. This should work even for |
238 | + non-interactive user interfaces. Typically this goes to a decorated |
239 | + version of stdout, but in a GUI it might be appropriate to send it to a |
240 | + window displaying the text. |
241 | + |
242 | + :param encoding: Unicode encoding for output; default is the |
243 | + terminal encoding, which may be different from the user encoding. |
244 | + (See get_terminal_encoding.) |
245 | + |
246 | + :param encoding_type: How to handle encoding errors: |
247 | + replace/strict/escape/exact. Default is replace. |
248 | + """ |
249 | + # XXX: is the caller supposed to close the resulting object? |
250 | + if encoding is None: |
251 | + encoding = osutils.get_terminal_encoding() |
252 | + if encoding_type is None: |
253 | + encoding_type = 'replace' |
254 | + out_stream = self._make_output_stream_explicit(encoding, encoding_type) |
255 | + return out_stream |
256 | + |
257 | + def _make_output_stream_explicit(self, encoding, encoding_type): |
258 | + raise NotImplementedError("%s doesn't support make_output_stream" |
259 | + % (self.__class__.__name__)) |
260 | + |
261 | def nested_progress_bar(self): |
262 | """Return a nested progress bar. |
263 | |
264 | |
265 | === modified file 'bzrlib/ui/text.py' |
266 | --- bzrlib/ui/text.py 2009-12-07 10:38:09 +0000 |
267 | +++ bzrlib/ui/text.py 2009-12-09 05:49:10 +0000 |
268 | @@ -18,6 +18,7 @@ |
269 | """Text UI, write output to the console. |
270 | """ |
271 | |
272 | +import codecs |
273 | import getpass |
274 | import os |
275 | import sys |
276 | @@ -146,6 +147,26 @@ |
277 | else: |
278 | return NullProgressView() |
279 | |
280 | + def _make_output_stream_explicit(self, encoding, encoding_type): |
281 | + if encoding_type == 'exact': |
282 | + # force sys.stdout to be binary stream on win32; |
283 | + # NB: this leaves the file set in that mode; may cause problems if |
284 | + # one process tries to do binary and then text output |
285 | + if sys.platform == 'win32': |
286 | + fileno = getattr(self.stdout, 'fileno', None) |
287 | + if fileno: |
288 | + import msvcrt |
289 | + msvcrt.setmode(fileno(), os.O_BINARY) |
290 | + return TextUIOutputStream(self, self.stdout) |
291 | + else: |
292 | + encoded_stdout = codecs.getwriter(encoding)(self.stdout, |
293 | + errors=encoding_type) |
294 | + # For whatever reason codecs.getwriter() does not advertise its encoding |
295 | + # it just returns the encoding of the wrapped file, which is completely |
296 | + # bogus. So set the attribute, so we can find the correct encoding later. |
297 | + encoded_stdout.encoding = encoding |
298 | + return TextUIOutputStream(self, encoded_stdout) |
299 | + |
300 | def note(self, msg): |
301 | """Write an already-formatted message, clearing the progress bar if necessary.""" |
302 | self.clear_term() |
303 | @@ -367,3 +388,37 @@ |
304 | self._bytes_since_update = 0 |
305 | self._last_transport_msg = msg |
306 | self._repaint() |
307 | + |
308 | + |
309 | +class TextUIOutputStream(object): |
310 | + """Decorates an output stream so that the terminal is cleared before writing. |
311 | + |
312 | + This is supposed to ensure that the progress bar does not conflict with bulk |
313 | + text output. |
314 | + """ |
315 | + # XXX: this does not handle the case of writing part of a line, then doing |
316 | + # progress bar output: the progress bar will probably write over it. |
317 | + # one option is just to buffer that text until we have a full line; |
318 | + # another is to save and restore it |
319 | + |
320 | + # XXX: might need to wrap more methods |
321 | + |
322 | + def __init__(self, ui_factory, wrapped_stream): |
323 | + self.ui_factory = ui_factory |
324 | + self.wrapped_stream = wrapped_stream |
325 | + # this does no transcoding, but it must expose the underlying encoding |
326 | + # because some callers need to know what can be written - see for |
327 | + # example unescape_for_display. |
328 | + self.encoding = getattr(wrapped_stream, 'encoding', None) |
329 | + |
330 | + def flush(self): |
331 | + self.ui_factory.clear_term() |
332 | + self.wrapped_stream.flush() |
333 | + |
334 | + def write(self, to_write): |
335 | + self.ui_factory.clear_term() |
336 | + self.wrapped_stream.write(to_write) |
337 | + |
338 | + def writelines(self, lines): |
339 | + self.ui_factory.clear_term() |
340 | + self.wrapped_stream.writelines(lines) |
This conceptually relates to <https:/ /code.edge. launchpad. net/~mbp/ bzr/remove- logging/ +merge/ 14942> but doesn't technically depend on it.
+* 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)
+
The idea is to
* Allow us to show transport activity whenever it happens, without clobbering the output of eg log - this doesn't actually restore this behaviour, but it gets ready for it.
* Move away from Command.outf, which assumes that it's only the command layer that knows about stdout. In fact, it's generally a layer or two down, and passing what's really a global is poor.
* Also avoid code defaulting directly to sys.stdout.
* In particular, put the unicode-handling features of outf into a more reusable place.
* Give GUIs a way to capture bulk output of application-level commands to show in a window.
Command.outf still works and redirects into this.