Code review comment for lp:~vila/bzr/856261-unshelve-line-based

Revision history for this message
Benoit Pierre (benoit.pierre) wrote :

Excerpts from Vincent Ladeuil's message of Tue Sep 27 16:37:19 UTC 2011:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/856261-unshelve-line-based into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> For more details, see:
> https://code.launchpad.net/~vila/bzr/856261-unshelve-line-based/+merge/77195
>
> I've been deprived from using shelve under emacs for years.
>
> But when I ran into bug #856261 trying to reproduce bug #850594, I thought
> it may be worth to have another go at it.
>
> The fix is simple: since osutils.getchar() requires a true term (which
> neither emacs not the test scripts provide), I handle the input as a
> line-based one which is exactly what emacs does and also exactly what the
> test script interface provides :)
>
> I'd like feedback on how we want to configure this feature, knowing that an
> environment variable may be more appropriate here (not everybody use
> terminals under emacs only and the test script need a way to force the
> behavior). Or should I just consider that the fix for #491196 is around the
> corner and name the config variable... shelve.line_based_input ?
> shelve.line_ui ? both boolean defaulting to False or shelve.input
> (defaulting to 'char' accepting 'line' ?)

I also ran into problems using shelve with my VIM plugin. Other commands
use ui.get_boolean, but there is no corresponding method for handling
more complex choices. So even after providing my own UI class
implementation, I cannot use shelve.

In VIM there is a confirm function:

confirm({msg} [, {choices} [, {default} [, {type}]]])

Which can be used like this:

confirm("Save changes?", "&Yes\n&No\n&Cancel")

Where the letter after the '&' is the shortcut key for that choice.

So I have been working on a branch to implement something just like
this (see attached patch).

N.B.: your patch removes a call to gettext.
--
A: Because it destroys the flow of conversation.
Q: Why is top posting dumb?

1# Bazaar merge directive format 2 (Bazaar 0.90)
2# revision_id: benoit.pierre@gmail.com-20110927192241-jfqqjzm2e6ojjfq0
3# target_branch: lp:bzr
4# testament_sha1: b3590b8caa798f8691f0e18ee303b9dbd983fd09
5# timestamp: 2011-09-27 21:23:43 +0200
6# base_revision_id: pqm@pqm.ubuntu.com-20110610161957-hh5ni839m7r3wsan
7#
8# Begin patch
9=== modified file 'bzrlib/shelf_ui.py'
10--- bzrlib/shelf_ui.py 2010-11-04 17:48:47 +0000
11+++ bzrlib/shelf_ui.py 2011-09-27 19:22:41 +0000
12@@ -251,23 +251,8 @@
13 diff_file.seek(0)
14 return patches.parse_patch(diff_file)
15
16- def prompt(self, message):
17- """Prompt the user for a character.
18-
19- :param message: The message to prompt a user with.
20- :return: A character.
21- """
22- if not sys.stdin.isatty():
23- # Since there is no controlling terminal we will hang when trying
24- # to prompt the user, better abort now. See
25- # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
26- # for more context.
27- raise errors.BzrError("You need a controlling terminal.")
28- sys.stdout.write(message)
29- char = osutils.getchar()
30- sys.stdout.write("\r" + ' ' * len(message) + '\r')
31- sys.stdout.flush()
32- return char
33+ def prompt(self, message, choices, default):
34+ return ui.ui_factory.confirm(message, choices, default=default)
35
36 def prompt_bool(self, question, long=False, allow_editor=False):
37 """Prompt the user with a yes/no question.
38@@ -279,16 +264,11 @@
39 """
40 if self.auto:
41 return True
42- editor_string = ''
43- if long:
44- if allow_editor:
45- editor_string = '(E)dit manually, '
46- prompt = ' [(y)es, (N)o, %s(f)inish, or (q)uit]' % editor_string
47- else:
48- if allow_editor:
49- editor_string = 'e'
50- prompt = ' [yN%sfq?]' % editor_string
51- char = self.prompt(question + prompt)
52+ choices = '&yes\n&No'
53+ if allow_editor:
54+ choices += '\n&edit manually'
55+ choices += '\n&finish\n&quit'
56+ char = self.prompt(question, choices, 'n')
57 if char == 'y':
58 return True
59 elif char == 'e' and allow_editor:
60
61=== modified file 'bzrlib/tests/blackbox/test_break_lock.py'
62--- bzrlib/tests/blackbox/test_break_lock.py 2011-03-10 13:29:54 +0000
63+++ bzrlib/tests/blackbox/test_break_lock.py 2011-09-27 19:22:41 +0000
64@@ -91,7 +91,7 @@
65 self.master_branch.lock_write()
66 # run the break-lock
67 # we need 5 yes's - wt, branch, repo, bound branch, bound repo.
68- self.run_bzr('break-lock checkout', stdin="y\ny\ny\ny\n")
69+ self.run_bzr('break-lock checkout', stdin="yyyy")
70 # a new tree instance should be lockable
71 br = branch.Branch.open('checkout')
72 br.lock_write()
73
74=== modified file 'bzrlib/tests/blackbox/test_uncommit.py'
75--- bzrlib/tests/blackbox/test_uncommit.py 2010-09-15 09:35:42 +0000
76+++ bzrlib/tests/blackbox/test_uncommit.py 2011-09-27 19:22:41 +0000
77@@ -72,7 +72,7 @@
78 $ bzr uncommit
79 ...
80 The above revision(s) will be removed.
81- 2>Uncommit these revisions? [y/n]:
82+ 2>Uncommit these revisions? ([y]es, [n]o): n
83 <n
84 Canceled
85 """)
86
87=== modified file 'bzrlib/tests/test_script.py'
88--- bzrlib/tests/test_script.py 2011-05-16 13:39:39 +0000
89+++ bzrlib/tests/test_script.py 2011-09-27 19:22:41 +0000
90@@ -580,12 +580,12 @@
91 self.addCleanup(commands.builtin_command_registry.remove, 'test-confirm')
92 self.run_script("""
93 $ bzr test-confirm
94- 2>Really do it? [y/n]:
95- <yes
96+ 2>Really do it? ([y]es, [n]o): y
97+ <y
98 Do it!
99 $ bzr test-confirm
100- 2>Really do it? [y/n]:
101- <no
102+ 2>Really do it? ([y]es, [n]o): n
103+ <n
104 ok, no
105 """)
106
107
108=== modified file 'bzrlib/tests/test_shelf_ui.py'
109--- bzrlib/tests/test_shelf_ui.py 2011-06-09 14:40:22 +0000
110+++ bzrlib/tests/test_shelf_ui.py 2011-09-27 19:22:41 +0000
111@@ -41,16 +41,18 @@
112 self.expected = []
113 self.diff_writer = StringIO()
114
115- def expect(self, prompt, response):
116- self.expected.append((prompt, response))
117+ def expect(self, message, choices, response):
118+ self.expected.append((message, choices, response))
119
120- def prompt(self, message):
121+ def prompt(self, message, choices, default):
122 try:
123- prompt, response = self.expected.pop(0)
124+ expected_message, expected_choices, response = self.expected.pop(0)
125 except IndexError:
126 raise AssertionError('Unexpected prompt: %s' % message)
127- if prompt != message:
128+ if message != expected_message:
129 raise AssertionError('Wrong prompt: %s' % message)
130+ if choices != expected_choices:
131+ raise AssertionError('Wrong choices: %s' % choices)
132 return response
133
134
135@@ -83,7 +85,7 @@
136 shelver = ExpectShelver(tree, tree.basis_tree())
137 self.addCleanup(shelver.finalize)
138 e = self.assertRaises(AssertionError, shelver.run)
139- self.assertEqual('Unexpected prompt: Shelve? [yNfq?]', str(e))
140+ self.assertEqual('Unexpected prompt: Shelve?', str(e))
141
142 def test_wrong_prompt_failure(self):
143 tree = self.create_shelvable_tree()
144@@ -91,9 +93,9 @@
145 self.addCleanup(tree.unlock)
146 shelver = ExpectShelver(tree, tree.basis_tree())
147 self.addCleanup(shelver.finalize)
148- shelver.expect('foo', 'y')
149+ shelver.expect('foo', 'bar', 'y')
150 e = self.assertRaises(AssertionError, shelver.run)
151- self.assertEqual('Wrong prompt: Shelve? [yNfq?]', str(e))
152+ self.assertEqual('Wrong prompt: Shelve?', str(e))
153
154 def test_shelve_not_diff(self):
155 tree = self.create_shelvable_tree()
156@@ -101,8 +103,8 @@
157 self.addCleanup(tree.unlock)
158 shelver = ExpectShelver(tree, tree.basis_tree())
159 self.addCleanup(shelver.finalize)
160- shelver.expect('Shelve? [yNfq?]', 'n')
161- shelver.expect('Shelve? [yNfq?]', 'n')
162+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
163+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
164 # No final shelving prompt because no changes were selected
165 shelver.run()
166 self.assertFileEqual(LINES_ZY, 'tree/foo')
167@@ -113,9 +115,9 @@
168 self.addCleanup(tree.unlock)
169 shelver = ExpectShelver(tree, tree.basis_tree())
170 self.addCleanup(shelver.finalize)
171- shelver.expect('Shelve? [yNfq?]', 'y')
172- shelver.expect('Shelve? [yNfq?]', 'y')
173- shelver.expect('Shelve 2 change(s)? [yNfq?]', 'n')
174+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
175+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
176+ shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'n')
177 shelver.run()
178 self.assertFileEqual(LINES_ZY, 'tree/foo')
179
180@@ -125,9 +127,9 @@
181 self.addCleanup(tree.unlock)
182 shelver = ExpectShelver(tree, tree.basis_tree())
183 self.addCleanup(shelver.finalize)
184- shelver.expect('Shelve? [yNfq?]', 'y')
185- shelver.expect('Shelve? [yNfq?]', 'y')
186- shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
187+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
188+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
189+ shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
190 shelver.run()
191 self.assertFileEqual(LINES_AJ, 'tree/foo')
192
193@@ -137,9 +139,9 @@
194 self.addCleanup(tree.unlock)
195 shelver = ExpectShelver(tree, tree.basis_tree())
196 self.addCleanup(shelver.finalize)
197- shelver.expect('Shelve? [yNfq?]', 'y')
198- shelver.expect('Shelve? [yNfq?]', 'n')
199- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
200+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
201+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'n')
202+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
203 shelver.run()
204 self.assertFileEqual(LINES_AY, 'tree/foo')
205
206@@ -150,8 +152,8 @@
207 self.addCleanup(tree.unlock)
208 shelver = ExpectShelver(tree, tree.basis_tree())
209 self.addCleanup(shelver.finalize)
210- shelver.expect('Shelve binary changes? [yNfq?]', 'y')
211- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
212+ shelver.expect('Shelve binary changes?', '&yes\n&No\n&finish\n&quit', 'y')
213+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
214 shelver.run()
215 self.assertFileEqual(LINES_AJ, 'tree/foo')
216
217@@ -162,10 +164,10 @@
218 self.addCleanup(tree.unlock)
219 shelver = ExpectShelver(tree, tree.basis_tree())
220 self.addCleanup(shelver.finalize)
221- shelver.expect('Shelve renaming "foo" => "bar"? [yNfq?]', 'y')
222- shelver.expect('Shelve? [yNfq?]', 'y')
223- shelver.expect('Shelve? [yNfq?]', 'y')
224- shelver.expect('Shelve 3 change(s)? [yNfq?]', 'y')
225+ shelver.expect('Shelve renaming "foo" => "bar"?', '&yes\n&No\n&finish\n&quit', 'y')
226+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
227+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'y')
228+ shelver.expect('Shelve 3 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
229 shelver.run()
230 self.assertFileEqual(LINES_AJ, 'tree/foo')
231
232@@ -176,8 +178,8 @@
233 self.addCleanup(tree.unlock)
234 shelver = ExpectShelver(tree, tree.basis_tree())
235 self.addCleanup(shelver.finalize)
236- shelver.expect('Shelve removing file "foo"? [yNfq?]', 'y')
237- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
238+ shelver.expect('Shelve removing file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
239+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
240 shelver.run()
241 self.assertFileEqual(LINES_AJ, 'tree/foo')
242
243@@ -190,8 +192,8 @@
244 self.addCleanup(tree.unlock)
245 shelver = ExpectShelver(tree, tree.basis_tree())
246 self.addCleanup(shelver.finalize)
247- shelver.expect('Shelve adding file "foo"? [yNfq?]', 'y')
248- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
249+ shelver.expect('Shelve adding file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
250+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
251 shelver.run()
252 self.assertPathDoesNotExist('tree/foo')
253
254@@ -203,9 +205,9 @@
255 self.addCleanup(tree.unlock)
256 shelver = ExpectShelver(tree, tree.basis_tree())
257 self.addCleanup(shelver.finalize)
258- shelver.expect('Shelve changing "foo" from file to directory? [yNfq?]',
259+ shelver.expect('Shelve changing "foo" from file to directory?', '&yes\n&No\n&finish\n&quit',
260 'y')
261- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
262+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
263
264 def test_shelve_modify_target(self):
265 self.requireFeature(tests.SymlinkFeature)
266@@ -220,8 +222,8 @@
267 shelver = ExpectShelver(tree, tree.basis_tree())
268 self.addCleanup(shelver.finalize)
269 shelver.expect('Shelve changing target of "baz" from "bar" to '
270- '"vax"? [yNfq?]', 'y')
271- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
272+ '"vax"?', '&yes\n&No\n&finish\n&quit', 'y')
273+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
274 shelver.run()
275 self.assertEqual('bar', os.readlink('tree/baz'))
276
277@@ -231,8 +233,8 @@
278 self.addCleanup(tree.unlock)
279 shelver = ExpectShelver(tree, tree.basis_tree())
280 self.addCleanup(shelver.finalize)
281- shelver.expect('Shelve? [yNfq?]', 'f')
282- shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
283+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'f')
284+ shelver.expect('Shelve 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
285 shelver.run()
286 self.assertFileEqual(LINES_AJ, 'tree/foo')
287
288@@ -242,7 +244,7 @@
289 self.addCleanup(tree.unlock)
290 shelver = ExpectShelver(tree, tree.basis_tree())
291 self.addCleanup(shelver.finalize)
292- shelver.expect('Shelve? [yNfq?]', 'q')
293+ shelver.expect('Shelve?', '&yes\n&No\n&finish\n&quit', 'q')
294 self.assertRaises(errors.UserAbort, shelver.run)
295 self.assertFileEqual(LINES_ZY, 'tree/foo')
296
297@@ -264,19 +266,8 @@
298 self.addCleanup(tree.unlock)
299 shelver = ExpectShelver(tree, tree.basis_tree(), file_list=['bar'])
300 self.addCleanup(shelver.finalize)
301- shelver.expect('Shelve adding file "bar"? [yNfq?]', 'y')
302- shelver.expect('Shelve 1 change(s)? [yNfq?]', 'y')
303- shelver.run()
304-
305- def test_shelve_help(self):
306- tree = self.create_shelvable_tree()
307- tree.lock_tree_write()
308- self.addCleanup(tree.unlock)
309- shelver = ExpectShelver(tree, tree.basis_tree())
310- self.addCleanup(shelver.finalize)
311- shelver.expect('Shelve? [yNfq?]', '?')
312- shelver.expect('Shelve? [(y)es, (N)o, (f)inish, or (q)uit]', 'f')
313- shelver.expect('Shelve 2 change(s)? [yNfq?]', 'y')
314+ shelver.expect('Shelve adding file "bar"?', '&yes\n&No\n&finish\n&quit', 'y')
315+ shelver.expect('Shelve 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
316 shelver.run()
317
318 def test_shelve_destroy(self):
319@@ -339,8 +330,8 @@
320 shelver = ExpectShelver(tree, tree.basis_tree(),
321 reporter=shelf_ui.ApplyReporter())
322 self.addCleanup(shelver.finalize)
323- shelver.expect('Apply change? [yNfq?]', 'n')
324- shelver.expect('Apply change? [yNfq?]', 'n')
325+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'n')
326+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'n')
327 # No final shelving prompt because no changes were selected
328 shelver.run()
329 self.assertFileEqual(LINES_ZY, 'tree/foo')
330@@ -352,9 +343,9 @@
331 shelver = ExpectShelver(tree, tree.basis_tree(),
332 reporter=shelf_ui.ApplyReporter())
333 self.addCleanup(shelver.finalize)
334- shelver.expect('Apply change? [yNfq?]', 'y')
335- shelver.expect('Apply change? [yNfq?]', 'y')
336- shelver.expect('Apply 2 change(s)? [yNfq?]', 'n')
337+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
338+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
339+ shelver.expect('Apply 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'n')
340 shelver.run()
341 self.assertFileEqual(LINES_ZY, 'tree/foo')
342
343@@ -365,9 +356,9 @@
344 shelver = ExpectShelver(tree, tree.basis_tree(),
345 reporter=shelf_ui.ApplyReporter())
346 self.addCleanup(shelver.finalize)
347- shelver.expect('Apply change? [yNfq?]', 'y')
348- shelver.expect('Apply change? [yNfq?]', 'y')
349- shelver.expect('Apply 2 change(s)? [yNfq?]', 'y')
350+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
351+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
352+ shelver.expect('Apply 2 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
353 shelver.run()
354 self.assertFileEqual(LINES_AJ, 'tree/foo')
355
356@@ -379,8 +370,8 @@
357 shelver = ExpectShelver(tree, tree.basis_tree(),
358 reporter=shelf_ui.ApplyReporter())
359 self.addCleanup(shelver.finalize)
360- shelver.expect('Apply binary changes? [yNfq?]', 'y')
361- shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
362+ shelver.expect('Apply binary changes?', '&yes\n&No\n&finish\n&quit', 'y')
363+ shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
364 shelver.run()
365 self.assertFileEqual(LINES_AJ, 'tree/foo')
366
367@@ -392,10 +383,10 @@
368 shelver = ExpectShelver(tree, tree.basis_tree(),
369 reporter=shelf_ui.ApplyReporter())
370 self.addCleanup(shelver.finalize)
371- shelver.expect('Rename "bar" => "foo"? [yNfq?]', 'y')
372- shelver.expect('Apply change? [yNfq?]', 'y')
373- shelver.expect('Apply change? [yNfq?]', 'y')
374- shelver.expect('Apply 3 change(s)? [yNfq?]', 'y')
375+ shelver.expect('Rename "bar" => "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
376+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
377+ shelver.expect('Apply change?', '&yes\n&No\n&finish\n&quit', 'y')
378+ shelver.expect('Apply 3 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
379 shelver.run()
380 self.assertFileEqual(LINES_AJ, 'tree/foo')
381
382@@ -407,8 +398,8 @@
383 shelver = ExpectShelver(tree, tree.basis_tree(),
384 reporter=shelf_ui.ApplyReporter())
385 self.addCleanup(shelver.finalize)
386- shelver.expect('Add file "foo"? [yNfq?]', 'y')
387- shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
388+ shelver.expect('Add file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
389+ shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
390 shelver.run()
391 self.assertFileEqual(LINES_AJ, 'tree/foo')
392
393@@ -422,8 +413,8 @@
394 shelver = ExpectShelver(tree, tree.basis_tree(),
395 reporter=shelf_ui.ApplyReporter())
396 self.addCleanup(shelver.finalize)
397- shelver.expect('Delete file "foo"? [yNfq?]', 'y')
398- shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
399+ shelver.expect('Delete file "foo"?', '&yes\n&No\n&finish\n&quit', 'y')
400+ shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
401 shelver.run()
402 self.assertPathDoesNotExist('tree/foo')
403
404@@ -436,8 +427,8 @@
405 shelver = ExpectShelver(tree, tree.basis_tree(),
406 reporter=shelf_ui.ApplyReporter())
407 self.addCleanup(shelver.finalize)
408- shelver.expect('Change "foo" from directory to a file? [yNfq?]', 'y')
409- shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
410+ shelver.expect('Change "foo" from directory to a file?', '&yes\n&No\n&finish\n&quit', 'y')
411+ shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
412
413 def test_shelve_modify_target(self):
414 self.requireFeature(tests.SymlinkFeature)
415@@ -452,9 +443,9 @@
416 shelver = ExpectShelver(tree, tree.basis_tree(),
417 reporter=shelf_ui.ApplyReporter())
418 self.addCleanup(shelver.finalize)
419- shelver.expect('Change target of "baz" from "vax" to "bar"? [yNfq?]',
420+ shelver.expect('Change target of "baz" from "vax" to "bar"?', '&yes\n&No\n&finish\n&quit',
421 'y')
422- shelver.expect('Apply 1 change(s)? [yNfq?]', 'y')
423+ shelver.expect('Apply 1 change(s)?', '&yes\n&No\n&finish\n&quit', 'y')
424 shelver.run()
425 self.assertEqual('bar', os.readlink('tree/baz'))
426
427
428=== modified file 'bzrlib/tests/test_ui.py'
429--- bzrlib/tests/test_ui.py 2011-05-16 13:39:39 +0000
430+++ bzrlib/tests/test_ui.py 2011-09-27 19:22:41 +0000
431@@ -109,13 +109,14 @@
432 pb.finished()
433
434 def test_text_ui_get_boolean(self):
435- stdin = tests.StringIOWrapper("y\n" # True
436- "n\n" # False
437- "yes with garbage\nY\n" # True
438- "not an answer\nno\n" # False
439- "I'm sure!\nyes\n" # True
440- "NO\n" # False
441- "foo\n")
442+ stdin = tests.StringIOWrapper("y" # True
443+ "n" # False
444+ "Y" # True
445+ "N" # False
446+ "\r\ngarbagey" # True
447+ "\r\ngarbagen" # False
448+ "foo\n"
449+ )
450 stdout = tests.StringIOWrapper()
451 stderr = tests.StringIOWrapper()
452 factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
453@@ -154,7 +155,7 @@
454 out = test_progress._TTYStringIO()
455 self.overrideEnv('TERM', 'xterm')
456 factory = _mod_ui_text.TextUIFactory(
457- stdin=tests.StringIOWrapper("yada\ny\n"),
458+ stdin=tests.StringIOWrapper("bar\ny"),
459 stdout=out, stderr=out)
460 factory._avail_width = lambda: 79
461 pb = factory.nested_progress_bar()
462@@ -171,7 +172,7 @@
463 self.assertContainsRe(output,
464 "| foo *\r\r *\r*")
465 self.assertContainsRe(output,
466- r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
467+ r"what do you want\? \(\[y\]es, \[n\]o\): y")
468 # stdin should have been totally consumed
469 self.assertEqual('', factory.stdin.readline())
470
471
472=== modified file 'bzrlib/ui/__init__.py'
473--- bzrlib/ui/__init__.py 2011-05-27 05:16:48 +0000
474+++ bzrlib/ui/__init__.py 2011-09-27 19:22:41 +0000
475@@ -315,6 +315,9 @@
476 warnings.warn(fail) # so tests will fail etc
477 return fail
478
479+ def confirm(self, msg, choices, default=None):
480+ raise NotImplementedError(self.confirm)
481+
482 def get_boolean(self, prompt):
483 """Get a boolean question answered from the user.
484
485@@ -322,7 +325,8 @@
486 line without terminating \\n.
487 :return: True or False for y/yes or n/no.
488 """
489- raise NotImplementedError(self.get_boolean)
490+ char = self.confirm(prompt + '?', '&yes\n&no', default=None)
491+ return 'y' == char
492
493 def get_integer(self, prompt):
494 """Get an integer from the user.
495@@ -477,6 +481,9 @@
496 def confirm_action(self, prompt, confirmation_id, args):
497 return self.get_boolean(prompt % args)
498
499+ def confirm(self, msg, choices, default=None):
500+ return self.responses.pop(0)
501+
502 def get_boolean(self, prompt):
503 return self.responses.pop(0)
504
505
506=== modified file 'bzrlib/ui/text.py'
507--- bzrlib/ui/text.py 2011-05-16 13:39:39 +0000
508+++ bzrlib/ui/text.py 2011-09-27 19:22:41 +0000
509@@ -30,6 +30,7 @@
510
511 from bzrlib import (
512 debug,
513+ errors,
514 progress,
515 osutils,
516 trace,
517@@ -61,6 +62,40 @@
518 # paints progress, network activity, etc
519 self._progress_view = self.make_progress_view()
520
521+ def confirm(self, msg, choices, default=None):
522+ if self.stdin == sys.stdin and self.stdin.isatty():
523+ def getchar():
524+ return osutils.getchar()
525+ else:
526+ def getchar():
527+ return self.stdin.read(1)
528+
529+ shortcuts = ''
530+ alternatives = []
531+ for c in choices.split('\n'):
532+ shortcut = c.find('&')
533+ if -1 != shortcut and (shortcut + 1) < len(c):
534+ help = c[:shortcut]
535+ help += '[' + c[shortcut + 1] + ']'
536+ help += c[(shortcut + 2):]
537+ shortcut = c[shortcut + 1]
538+ else:
539+ help = c.strip('&')
540+ shortcut = c[0]
541+ shortcuts += shortcut.lower()
542+ alternatives.append(help)
543+
544+ prompt = u'%s (%s): ' % (msg, ', '.join(alternatives))
545+ self.prompt(prompt)
546+
547+ while True:
548+ char = getchar().lower()
549+ if '\r' == char and default is not None:
550+ char = default
551+ if -1 != shortcuts.find(char):
552+ self.stderr.write(char + '\n')
553+ return char
554+
555 def be_quiet(self, state):
556 if state and not self._quiet:
557 self.clear_term()
558@@ -78,18 +113,6 @@
559 # to clear it. We might need to separately check for the case of
560 self._progress_view.clear()
561
562- def get_boolean(self, prompt):
563- while True:
564- self.prompt(prompt + "? [y/n]: ")
565- line = self.stdin.readline().lower()
566- if line in ('y\n', 'yes\n'):
567- return True
568- elif line in ('n\n', 'no\n'):
569- return False
570- elif line in ('', None):
571- # end-of-file; possibly should raise an error here instead
572- return None
573-
574 def get_integer(self, prompt):
575 while True:
576 self.prompt(prompt)
577
578# Begin bundle
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

« Back to merge proposal