~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/__init__.py

Merge up through 2.2.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
22
22
Several levels are supported, and you can also register new factories such as
23
23
for a GUI.
24
24
 
25
 
UIFactory
 
25
bzrlib.ui.UIFactory
26
26
    Semi-abstract base class
27
27
 
28
 
SilentUIFactory
 
28
bzrlib.ui.SilentUIFactory
29
29
    Produces no output and cannot take any input; useful for programs using
30
30
    bzrlib in batch mode or for programs such as loggerhead.
31
31
 
32
 
CannedInputUIFactory
 
32
bzrlib.ui.CannedInputUIFactory
33
33
    For use in testing; the input values to be returned are provided 
34
34
    at construction.
35
35
 
36
 
TextUIFactory
 
36
bzrlib.ui.text.TextUIFactory
37
37
    Standard text command-line interface, with stdin, stdout, stderr.
38
38
    May make more or less advanced use of them, eg in drawing progress bars,
39
39
    depending on the detected capabilities of the terminal.
105
105
 
106
106
    This tells the library how to display things to the user.  Through this
107
107
    layer different applications can choose the style of UI.
 
108
 
 
109
    UI Factories are also context managers, for some syntactic sugar some users
 
110
    need.
 
111
 
 
112
    :ivar suppressed_warnings: Identifiers for user warnings that should 
 
113
        no be emitted.
108
114
    """
109
115
 
 
116
    _user_warning_templates = dict(
 
117
        cross_format_fetch=("Doing on-the-fly conversion from "
 
118
            "%(from_format)s to %(to_format)s.\n"
 
119
            "This may take some time. Upgrade the repositories to the "
 
120
            "same format for better performance."
 
121
            )
 
122
        )
 
123
 
110
124
    def __init__(self):
111
125
        self._task_stack = []
 
126
        self.suppressed_warnings = set()
 
127
        self._quiet = False
 
128
 
 
129
    def __enter__(self):
 
130
        """Context manager entry support.
 
131
 
 
132
        Override in a concrete factory class if initialisation before use is
 
133
        needed.
 
134
        """
 
135
        return self # This is bound to the 'as' clause in a with statement.
 
136
 
 
137
    def __exit__(self, exc_type, exc_val, exc_tb):
 
138
        """Context manager exit support.
 
139
 
 
140
        Override in a concrete factory class if more cleanup than a simple
 
141
        self.clear_term() is needed when the UIFactory is finished with.
 
142
        """
 
143
        self.clear_term()
 
144
        return False # propogate exceptions.
 
145
 
 
146
    def be_quiet(self, state):
 
147
        """Tell the UI to be more quiet, or not.
 
148
 
 
149
        Typically this suppresses progress bars; the application may also look
 
150
        at ui_factory.is_quiet().
 
151
        """
 
152
        self._quiet = state
112
153
 
113
154
    def get_password(self, prompt='', **kwargs):
114
155
        """Prompt the user for a password.
125
166
        """
126
167
        raise NotImplementedError(self.get_password)
127
168
 
 
169
    def is_quiet(self):
 
170
        return self._quiet
 
171
 
 
172
    def make_output_stream(self, encoding=None, encoding_type=None):
 
173
        """Get a stream for sending out bulk text data.
 
174
 
 
175
        This is used for commands that produce bulk text, such as log or diff
 
176
        output, as opposed to user interaction.  This should work even for
 
177
        non-interactive user interfaces.  Typically this goes to a decorated
 
178
        version of stdout, but in a GUI it might be appropriate to send it to a 
 
179
        window displaying the text.
 
180
     
 
181
        :param encoding: Unicode encoding for output; if not specified 
 
182
            uses the configured 'output_encoding' if any; otherwise the 
 
183
            terminal encoding. 
 
184
            (See get_terminal_encoding.)
 
185
 
 
186
        :param encoding_type: How to handle encoding errors:
 
187
            replace/strict/escape/exact.  Default is replace.
 
188
        """
 
189
        # XXX: is the caller supposed to close the resulting object?
 
190
        if encoding is None:
 
191
            from bzrlib import config
 
192
            encoding = config.GlobalConfig().get_user_option(
 
193
                'output_encoding')
 
194
        if encoding is None:
 
195
            encoding = osutils.get_terminal_encoding(trace=True)
 
196
        if encoding_type is None:
 
197
            encoding_type = 'replace'
 
198
        out_stream = self._make_output_stream_explicit(encoding, encoding_type)
 
199
        return out_stream
 
200
 
 
201
    def _make_output_stream_explicit(self, encoding, encoding_type):
 
202
        raise NotImplementedError("%s doesn't support make_output_stream"
 
203
            % (self.__class__.__name__))
 
204
 
128
205
    def nested_progress_bar(self):
129
206
        """Return a nested progress bar.
130
207
 
143
220
        if not self._task_stack:
144
221
            warnings.warn("%r finished but nothing is active"
145
222
                % (task,))
146
 
        elif task != self._task_stack[-1]:
147
 
            warnings.warn("%r is not the active task %r"
148
 
                % (task, self._task_stack[-1]))
 
223
        if task in self._task_stack:
 
224
            self._task_stack.remove(task)
149
225
        else:
150
 
            del self._task_stack[-1]
 
226
            warnings.warn("%r is not in active stack %r"
 
227
                % (task, self._task_stack))
151
228
        if not self._task_stack:
152
229
            self._progress_all_finished()
153
230
 
170
247
        """
171
248
        pass
172
249
 
 
250
    def format_user_warning(self, warning_id, message_args):
 
251
        try:
 
252
            template = self._user_warning_templates[warning_id]
 
253
        except KeyError:
 
254
            fail = "failed to format warning %r, %r" % (warning_id, message_args)
 
255
            warnings.warn(fail)   # so tests will fail etc
 
256
            return fail
 
257
        try:
 
258
            return template % message_args
 
259
        except ValueError, e:
 
260
            fail = "failed to format warning %r, %r: %s" % (
 
261
                warning_id, message_args, e)
 
262
            warnings.warn(fail)   # so tests will fail etc
 
263
            return fail
 
264
 
173
265
    def get_boolean(self, prompt):
174
266
        """Get a boolean question answered from the user.
175
267
 
179
271
        """
180
272
        raise NotImplementedError(self.get_boolean)
181
273
 
 
274
    def get_integer(self, prompt):
 
275
        """Get an integer from the user.
 
276
 
 
277
        :param prompt: a message to prompt the user with. Could be a multi-line
 
278
            prompt but without a terminating \n.
 
279
 
 
280
        :return: A signed integer.
 
281
        """
 
282
        raise NotImplementedError(self.get_integer)
 
283
 
182
284
    def make_progress_view(self):
183
285
        """Construct a new ProgressView object for this UI.
184
286
 
190
292
    def recommend_upgrade(self,
191
293
        current_format_name,
192
294
        basedir):
193
 
        # this should perhaps be in the TextUIFactory and the default can do
 
295
        # XXX: this should perhaps be in the TextUIFactory and the default can do
194
296
        # nothing
 
297
        #
 
298
        # XXX: Change to show_user_warning - that will accomplish the previous
 
299
        # xxx. -- mbp 2010-02-25
195
300
        trace.warning("%s is deprecated "
196
301
            "and a better format is available.\n"
197
302
            "It is recommended that you upgrade by "
208
313
        """
209
314
        pass
210
315
 
211
 
 
212
 
 
213
 
class CLIUIFactory(UIFactory):
214
 
    """Deprecated in favor of TextUIFactory."""
215
 
 
216
 
    @deprecated_method(deprecated_in((1, 18, 0)))
217
 
    def __init__(self, stdin=None, stdout=None, stderr=None):
218
 
        UIFactory.__init__(self)
219
 
        self.stdin = stdin or sys.stdin
220
 
        self.stdout = stdout or sys.stdout
221
 
        self.stderr = stderr or sys.stderr
222
 
 
223
 
    _accepted_boolean_strings = dict(y=True, n=False, yes=True, no=False)
224
 
 
225
 
    def get_boolean(self, prompt):
226
 
        while True:
227
 
            self.prompt(prompt + "? [y/n]: ")
228
 
            line = self.stdin.readline()
229
 
            line = line.rstrip('\n')
230
 
            val = bool_from_string(line, self._accepted_boolean_strings)
231
 
            if val is not None:
232
 
                return val
233
 
 
234
 
    def get_non_echoed_password(self):
235
 
        isatty = getattr(self.stdin, 'isatty', None)
236
 
        if isatty is not None and isatty():
237
 
            # getpass() ensure the password is not echoed and other
238
 
            # cross-platform niceties
239
 
            password = getpass.getpass('')
240
 
        else:
241
 
            # echo doesn't make sense without a terminal
242
 
            password = self.stdin.readline()
243
 
            if not password:
244
 
                password = None
245
 
            elif password[-1] == '\n':
246
 
                password = password[:-1]
247
 
        return password
248
 
 
249
 
    def get_password(self, prompt='', **kwargs):
250
 
        """Prompt the user for a password.
251
 
 
252
 
        :param prompt: The prompt to present the user
253
 
        :param kwargs: Arguments which will be expanded into the prompt.
254
 
                       This lets front ends display different things if
255
 
                       they so choose.
256
 
        :return: The password string, return None if the user
257
 
                 canceled the request.
258
 
        """
259
 
        prompt += ': '
260
 
        self.prompt(prompt, **kwargs)
261
 
        # There's currently no way to say 'i decline to enter a password'
262
 
        # as opposed to 'my password is empty' -- does it matter?
263
 
        return self.get_non_echoed_password()
264
 
 
265
 
    def get_username(self, prompt, **kwargs):
266
 
        """Prompt the user for a username.
267
 
 
268
 
        :param prompt: The prompt to present the user
269
 
        :param kwargs: Arguments which will be expanded into the prompt.
270
 
                       This lets front ends display different things if
271
 
                       they so choose.
272
 
        :return: The username string, return None if the user
273
 
                 canceled the request.
274
 
        """
275
 
        prompt += ': '
276
 
        self.prompt(prompt, **kwargs)
277
 
        username = self.stdin.readline()
278
 
        if not username:
279
 
            username = None
280
 
        elif username[-1] == '\n':
281
 
            username = username[:-1]
282
 
        return username
283
 
 
284
 
    def prompt(self, prompt, **kwargs):
285
 
        """Emit prompt on the CLI.
286
 
        
287
 
        :param kwargs: Dictionary of arguments to insert into the prompt,
288
 
            to allow UIs to reformat the prompt.
289
 
        """
290
 
        if kwargs:
291
 
            # See <https://launchpad.net/bugs/365891>
292
 
            prompt = prompt % kwargs
293
 
        prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
294
 
        self.clear_term()
295
 
        self.stderr.write(prompt)
296
 
 
297
 
    def note(self, msg):
298
 
        """Write an already-formatted message."""
299
 
        self.stdout.write(msg + '\n')
 
316
    def log_transport_activity(self, display=False):
 
317
        """Write out whatever transport activity has been measured.
 
318
 
 
319
        Implementations are allowed to do nothing, but it is useful if they can
 
320
        write a line to the log file.
 
321
 
 
322
        :param display: If False, only log to disk, if True also try to display
 
323
            a message to the user.
 
324
        :return: None
 
325
        """
 
326
        # Default implementation just does nothing
 
327
        pass
 
328
 
 
329
    def show_user_warning(self, warning_id, **message_args):
 
330
        """Show a warning to the user.
 
331
 
 
332
        This is specifically for things that are under the user's control (eg
 
333
        outdated formats), not for internal program warnings like deprecated
 
334
        APIs.
 
335
 
 
336
        This can be overridden by UIFactory subclasses to show it in some 
 
337
        appropriate way; the default UIFactory is noninteractive and does
 
338
        nothing.  format_user_warning maps it to a string, though other
 
339
        presentations can be used for particular UIs.
 
340
 
 
341
        :param warning_id: An identifier like 'cross_format_fetch' used to 
 
342
            check if the message is suppressed and to look up the string.
 
343
        :param message_args: Arguments to be interpolated into the message.
 
344
        """
 
345
        pass
 
346
 
 
347
    def show_error(self, msg):
 
348
        """Show an error message (not an exception) to the user.
 
349
        
 
350
        The message should not have an error prefix or trailing newline.  That
 
351
        will be added by the factory if appropriate.
 
352
        """
 
353
        raise NotImplementedError(self.show_error)
 
354
 
 
355
    def show_message(self, msg):
 
356
        """Show a message to the user."""
 
357
        raise NotImplementedError(self.show_message)
 
358
 
 
359
    def show_warning(self, msg):
 
360
        """Show a warning to the user."""
 
361
        raise NotImplementedError(self.show_warning)
 
362
 
 
363
    def warn_cross_format_fetch(self, from_format, to_format):
 
364
        """Warn about a potentially slow cross-format transfer.
 
365
        
 
366
        This is deprecated in favor of show_user_warning, but retained for api
 
367
        compatibility in 2.0 and 2.1.
 
368
        """
 
369
        self.show_user_warning('cross_format_fetch', from_format=from_format,
 
370
            to_format=to_format)
 
371
 
 
372
    def warn_experimental_format_fetch(self, inter):
 
373
        """Warn about fetching into experimental repository formats."""
 
374
        if inter.target._format.experimental:
 
375
            trace.warning("Fetching into experimental format %s.\n"
 
376
                "This format may be unreliable or change in the future "
 
377
                "without an upgrade path.\n" % (inter.target._format,))
300
378
 
301
379
 
302
380
class SilentUIFactory(UIFactory):
318
396
    def get_username(self, prompt, **kwargs):
319
397
        return None
320
398
 
 
399
    def _make_output_stream_explicit(self, encoding, encoding_type):
 
400
        return NullOutputStream(encoding)
 
401
 
 
402
    def show_error(self, msg):
 
403
        pass
 
404
 
 
405
    def show_message(self, msg):
 
406
        pass
 
407
 
 
408
    def show_warning(self, msg):
 
409
        pass
 
410
 
321
411
 
322
412
class CannedInputUIFactory(SilentUIFactory):
323
413
    """A silent UI that return canned input."""
331
421
    def get_boolean(self, prompt):
332
422
        return self.responses.pop(0)
333
423
 
 
424
    def get_integer(self, prompt):
 
425
        return self.responses.pop(0)
 
426
 
334
427
    def get_password(self, prompt='', **kwargs):
335
428
        return self.responses.pop(0)
336
429
 
337
430
    def get_username(self, prompt, **kwargs):
338
431
        return self.responses.pop(0)
339
 
    
 
432
 
340
433
    def assert_all_input_consumed(self):
341
434
        if self.responses:
342
435
            raise AssertionError("expected all input in %r to be consumed"
343
436
                % (self,))
344
437
 
345
438
 
346
 
@deprecated_function(deprecated_in((1, 18, 0)))
347
 
def clear_decorator(func, *args, **kwargs):
348
 
    """Decorator that clears the term"""
349
 
    ui_factory.clear_term()
350
 
    func(*args, **kwargs)
351
 
 
352
 
 
353
439
ui_factory = SilentUIFactory()
354
440
# IMPORTANT: never import this symbol directly. ONLY ever access it as
355
441
# ui.ui_factory, so that you refer to the current value.
375
461
 
376
462
    def show_transport_activity(self, transport, direction, byte_count):
377
463
        pass
 
464
 
 
465
    def log_transport_activity(self, display=False):
 
466
        pass
 
467
 
 
468
 
 
469
class NullOutputStream(object):
 
470
    """Acts like a file, but discard all output."""
 
471
 
 
472
    def __init__(self, encoding):
 
473
        self.encoding = encoding
 
474
 
 
475
    def write(self, data):
 
476
        pass
 
477
 
 
478
    def writelines(self, data):
 
479
        pass
 
480
 
 
481
    def close(self):
 
482
        pass